|
41 | 41 | %% Close and drain tests |
42 | 42 | close_drain_bytes_erlang_test/1, |
43 | 43 | close_drain_bytes_python_sync_test/1, |
44 | | - close_drain_bytes_python_async_test/1 |
| 44 | + close_drain_bytes_python_async_test/1, |
| 45 | + close_drain_bytes_create_task_async_test/1 |
45 | 46 | ]). |
46 | 47 |
|
47 | 48 | all() -> [ |
@@ -72,7 +73,8 @@ all() -> [ |
72 | 73 | %% Close and drain tests |
73 | 74 | close_drain_bytes_erlang_test, |
74 | 75 | close_drain_bytes_python_sync_test, |
75 | | - close_drain_bytes_python_async_test |
| 76 | + close_drain_bytes_python_async_test, |
| 77 | + close_drain_bytes_create_task_async_test |
76 | 78 | ]. |
77 | 79 |
|
78 | 80 | init_per_suite(Config) -> |
@@ -530,3 +532,63 @@ async def async_drain_byte_channel(ch_ref): |
530 | 532 | py:eval(Ctx, <<"erlang.run(async_drain_byte_channel(ch))">>, #{<<"ch">> => Ch}), |
531 | 533 |
|
532 | 534 | ct:pal("Python async byte channel close+drain test passed"). |
| 535 | + |
| 536 | +%% @doc Test async drain with create_task when data arrives after task starts |
| 537 | +%% This tests the notification callback path: task registers waiter, then data arrives |
| 538 | +close_drain_bytes_create_task_async_test(_Config) -> |
| 539 | + {ok, Ch} = py_byte_channel:new(), |
| 540 | + |
| 541 | + %% Define the async drain task |
| 542 | + Ctx = py:context(1), |
| 543 | + ok = py:exec(Ctx, <<" |
| 544 | +import erlang |
| 545 | +from erlang import ByteChannel |
| 546 | +
|
| 547 | +async def drain_task(ch_ref, reply_pid): |
| 548 | + '''Task that drains byte channel and sends results back.''' |
| 549 | + try: |
| 550 | + ch = ByteChannel(ch_ref) |
| 551 | + chunks = [] |
| 552 | + async for chunk in ch: |
| 553 | + chunks.append(chunk) |
| 554 | + erlang.send(reply_pid, ('result', chunks)) |
| 555 | + except Exception as e: |
| 556 | + erlang.send(reply_pid, ('error', str(e))) |
| 557 | +">>), |
| 558 | + |
| 559 | + %% Create the task BEFORE sending any data |
| 560 | + %% This forces the task to register a waiter and wait for notifications |
| 561 | + TaskRef = py_event_loop:create_task( |
| 562 | + "__main__", "drain_task", [Ch, self()]), |
| 563 | + |
| 564 | + %% Give the task time to start and register waiter |
| 565 | + timer:sleep(100), |
| 566 | + |
| 567 | + %% Now send data - should trigger notification callback |
| 568 | + ok = py_byte_channel:send(Ch, <<"chunk1">>), |
| 569 | + ok = py_byte_channel:send(Ch, <<"chunk2">>), |
| 570 | + ok = py_byte_channel:send(Ch, <<"chunk3">>), |
| 571 | + |
| 572 | + %% Close the channel to signal end of stream |
| 573 | + ok = py_byte_channel:close(Ch), |
| 574 | + |
| 575 | + %% Wait for result from the task |
| 576 | + receive |
| 577 | + {<<"result">>, [<<"chunk1">>, <<"chunk2">>, <<"chunk3">>]} -> |
| 578 | + ct:pal("create_task async drain with delayed data OK"); |
| 579 | + {<<"result">>, Other} -> |
| 580 | + ct:pal("Unexpected result: ~p", [Other]), |
| 581 | + ct:fail({unexpected_result, Other}); |
| 582 | + {<<"error">>, ErrMsg} -> |
| 583 | + ct:pal("Task error: ~p", [ErrMsg]), |
| 584 | + ct:fail({task_error, ErrMsg}) |
| 585 | + after 5000 -> |
| 586 | + ct:fail("Timeout waiting for drain task result") |
| 587 | + end, |
| 588 | + |
| 589 | + %% Wait for task to complete |
| 590 | + case py_event_loop:await(TaskRef, 5000) of |
| 591 | + {ok, _} -> ok; |
| 592 | + {error, AwaitErr} -> |
| 593 | + ct:pal("Await error: ~p", [AwaitErr]) |
| 594 | + end. |
0 commit comments