7
7
TYPE_CHECKING ,
8
8
Any ,
9
9
Dict ,
10
+ List ,
10
11
Tuple ,
12
+ Union ,
11
13
cast ,
12
14
)
13
15
61
63
AsyncContract ,
62
64
AsyncContractFunction ,
63
65
)
66
+ from web3 .providers .persistent .subscription_manager import (
67
+ SubscriptionContainer ,
68
+ )
64
69
65
70
66
71
# LogIndexedAndNotIndexed event args
@@ -158,6 +163,12 @@ async def logs_handler(
158
163
assert await sub .unsubscribe ()
159
164
160
165
166
+ async def idle_handler (
167
+ _handler_context : Any ,
168
+ ) -> None :
169
+ pass
170
+
171
+
161
172
async def emit_contract_event (
162
173
async_w3 : AsyncWeb3 ,
163
174
acct : ChecksumAddress ,
@@ -194,6 +205,14 @@ async def log_indexed_and_non_indexed_args_task(
194
205
)
195
206
196
207
208
+ def assert_no_subscriptions_left (sub_container : "SubscriptionContainer" ) -> None :
209
+ assert len (sub_container ) == 0
210
+ assert len (sub_container .subscriptions ) == 0
211
+ assert len (sub_container .subscriptions_by_id ) == 0
212
+ assert len (sub_container .subscriptions_by_label ) == 0
213
+ assert len (sub_container .handler_subscriptions ) == 0
214
+
215
+
197
216
class PersistentConnectionProviderTest :
198
217
@staticmethod
199
218
async def seed_transactions_to_geth (
@@ -295,6 +314,13 @@ async def test_async_eth_subscribe_syncing_mocked(
295
314
await async_w3 .eth .unsubscribe (sub_id )
296
315
break
297
316
317
+ assert_no_subscriptions_left (
318
+ async_w3 .subscription_manager ._subscription_container
319
+ )
320
+
321
+ # cleanup
322
+ async_w3 .provider ._request_processor .clear_caches ()
323
+
298
324
@pytest .mark .asyncio
299
325
async def test_async_eth_subscribe_new_heads (self , async_w3 : AsyncWeb3 ) -> None :
300
326
sub_id = await async_w3 .eth .subscribe ("newHeads" )
@@ -308,14 +334,16 @@ async def test_async_eth_subscribe_new_heads(self, async_w3: AsyncWeb3) -> None:
308
334
break
309
335
310
336
assert await async_w3 .eth .unsubscribe (sub_id )
337
+ assert_no_subscriptions_left (
338
+ async_w3 .subscription_manager ._subscription_container
339
+ )
311
340
312
341
@pytest .mark .asyncio
313
342
async def test_async_eth_subscribe_creates_and_handles_new_heads_subscription_type (
314
343
self ,
315
344
async_w3 : AsyncWeb3 ,
316
345
) -> None :
317
346
sub_manager = async_w3 .subscription_manager
318
-
319
347
new_heads_handler_test = SubscriptionHandlerTest ()
320
348
321
349
sub_id = await async_w3 .eth .subscribe (
@@ -376,9 +404,9 @@ async def test_async_eth_subscribe_new_and_process_pending_tx_true(
376
404
377
405
# cleanup
378
406
assert await async_w3 .eth .unsubscribe (sub_id )
379
- assert len ( async_w3 . subscription_manager . subscriptions ) == 0
380
- assert len ( async_w3 .subscription_manager ._subscriptions_by_id ) == 0
381
- assert len ( async_w3 . subscription_manager . _subscriptions_by_label ) == 0
407
+ assert_no_subscriptions_left (
408
+ async_w3 .subscription_manager ._subscription_container
409
+ )
382
410
async_w3 .provider ._request_processor .clear_caches ()
383
411
await async_w3 .eth .wait_for_transaction_receipt (tx_hash )
384
412
tx_seeder_task .cancel ()
@@ -414,9 +442,9 @@ async def test_async_eth_subscribe_and_process_pending_tx_false(
414
442
415
443
# cleanup
416
444
await async_w3 .eth .unsubscribe (sub_id )
417
- assert len ( async_w3 . subscription_manager . subscriptions ) == 0
418
- assert len ( async_w3 .subscription_manager ._subscriptions_by_id ) == 0
419
- assert len ( async_w3 . subscription_manager . _subscriptions_by_label ) == 0
445
+ assert_no_subscriptions_left (
446
+ async_w3 .subscription_manager ._subscription_container
447
+ )
420
448
await async_w3 .eth .wait_for_transaction_receipt (tx_hash )
421
449
tx_seeder_task .cancel ()
422
450
@@ -762,3 +790,76 @@ async def test_sub_handler(
762
790
763
791
# cleanup
764
792
sub_manager .total_handler_calls = 0
793
+
794
+ @pytest .mark .asyncio
795
+ async def test_subscriptions_with_handler_and_without (
796
+ self , async_w3 : AsyncWeb3
797
+ ) -> None :
798
+ handler_test = SubscriptionHandlerTest ()
799
+ stream_passed = False
800
+
801
+ async def test_sub_handler (
802
+ handler_context : NewHeadsSubscriptionContext ,
803
+ ) -> None :
804
+ handler_context .handler_test .passed = True
805
+ await handler_context .subscription .unsubscribe ()
806
+
807
+ async def handle_subscription_stream () -> None :
808
+ nonlocal stream_passed
809
+ async for msg in async_w3 .socket .process_subscriptions ():
810
+ response = cast (FormattedEthSubscriptionResponse , msg )
811
+ assert sub_manager .get_by_id (response ["subscription" ]) is not None
812
+ assert response ["result" ] is not None
813
+ # wait for the handler to unsubscribe:
814
+ stream_passed = True
815
+ await async_w3 .eth .unsubscribe (response ["subscription" ])
816
+ break
817
+
818
+ await async_w3 .eth .subscribe (
819
+ "newHeads" ,
820
+ handler = test_sub_handler ,
821
+ label = "managed" ,
822
+ handler_context = {"handler_test" : handler_test },
823
+ )
824
+ await async_w3 .eth .subscribe ("newHeads" , label = "streamed" )
825
+
826
+ sub_manager = async_w3 .subscription_manager
827
+ assert len (sub_manager .subscriptions ) == 2
828
+
829
+ await asyncio .gather (
830
+ sub_manager .handle_subscriptions (),
831
+ handle_subscription_stream (),
832
+ )
833
+
834
+ assert len (sub_manager .subscriptions ) == 0
835
+ assert sub_manager .total_handler_calls == 1
836
+ assert handler_test .passed
837
+ assert stream_passed
838
+
839
+ # cleanup
840
+ sub_manager .total_handler_calls = 0
841
+
842
+ @pytest .mark .asyncio
843
+ async def test_handle_subscriptions_breaks_on_unsubscribe (
844
+ self ,
845
+ async_w3 : AsyncWeb3 ,
846
+ ) -> None :
847
+ async def unsubscribe_subs (
848
+ subs : List [Union [NewHeadsSubscription , LogsSubscription ]]
849
+ ) -> None :
850
+ for sub in subs :
851
+ await sub .unsubscribe ()
852
+
853
+ sub_manager = async_w3 .subscription_manager
854
+ sub1 = NewHeadsSubscription (label = "foo" , handler = idle_handler )
855
+ sub2 = LogsSubscription (label = "bar" , handler = idle_handler )
856
+ await sub_manager .subscribe ([sub1 , sub2 ])
857
+ assert sub_manager .subscriptions == [sub1 , sub2 ]
858
+
859
+ asyncio .create_task (unsubscribe_subs ([sub1 , sub2 ]))
860
+ # With no subscriptions in the queue, ``handle_subscriptions`` should hang
861
+ # indefinitely. Test that when the last subscription is unsubscribed from,
862
+ # the method breaks out of the loop. This is done via a raised
863
+ # ``SubscriptionProcessingFinished`` within the ``TaskReliantQueue``.
864
+ await sub_manager .handle_subscriptions ()
865
+ assert_no_subscriptions_left (sub_manager ._subscription_container )
0 commit comments