| 516 | } |
| 517 | |
| 518 | fn schedule_callbacks(zelf: &PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> { |
| 519 | // Collect all callbacks first to avoid holding locks during callback execution |
| 520 | // This prevents deadlock when callbacks access the future's properties |
| 521 | let mut callbacks_to_call: Vec<(PyObjectRef, Option<PyObjectRef>)> = Vec::new(); |
| 522 | |
| 523 | // Take callback0 - release lock before collecting from list |
| 524 | let cb0 = zelf.fut_callback0.write().take(); |
| 525 | let ctx0 = zelf.fut_context0.write().take(); |
| 526 | if let Some(cb) = cb0 { |
| 527 | callbacks_to_call.push((cb, ctx0)); |
| 528 | } |
| 529 | |
| 530 | // Take callbacks list and collect items |
| 531 | let callbacks_list = zelf.fut_callbacks.write().take(); |
| 532 | if let Some(callbacks) = callbacks_list |
| 533 | && let Ok(list) = callbacks.downcast::<PyList>() |
| 534 | { |
| 535 | // Clone the items while holding the list lock, then release |
| 536 | let items: Vec<_> = list.borrow_vec().iter().cloned().collect(); |
| 537 | for item in items { |
| 538 | if let Some(tuple) = item.downcast_ref::<PyTuple>() |
| 539 | && let (Some(cb), Some(ctx)) = (tuple.first(), tuple.get(1)) |
| 540 | { |
| 541 | callbacks_to_call.push((cb.clone(), Some(ctx.clone()))); |
| 542 | } |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | // Now call all callbacks without holding any locks |
| 547 | for (cb, ctx) in callbacks_to_call { |
| 548 | Self::call_soon_with_context(zelf, cb, ctx, vm)?; |
| 549 | } |
| 550 | |
| 551 | Ok(()) |
| 552 | } |
| 553 | |
| 554 | fn call_soon_with_context( |
| 555 | zelf: &PyRef<Self>, |