Skip to content
This repository was archived by the owner on Jul 10, 2026. It is now read-only.

Commit c8f66e0

Browse files
author
Loom Agent
committed
fix(async_subtensor): don't close shared connection in register methods
register, burned_register and register_limit used `async with self:`, whose __aexit__ calls self.substrate.close(). On a long-lived, shared subtensor this closed the caller's websocket as soon as the method returned, breaking every subsequent query. Every other async method already assumes an open connection and leaves it open; these three were the only outliers. Add an _ensure_connection async-context helper that initializes the substrate without closing it, and use it in place of `async with self:`.
1 parent b9af04a commit c8f66e0

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

bittensor/core/async_subtensor.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import contextlib
23
import copy
34
import ssl
45
from datetime import datetime, timezone
@@ -358,6 +359,19 @@ async def __aenter__(self):
358359
async def __aexit__(self, exc_type, exc_val, exc_tb):
359360
await self.substrate.close()
360361

362+
@contextlib.asynccontextmanager
363+
async def _ensure_connection(self):
364+
"""Ensure the substrate connection is open for the duration of a single call
365+
without closing it afterwards.
366+
367+
Unlike ``async with self:``, this must be used by methods that run on a
368+
long-lived, shared connection: ``__aexit__`` calls ``self.substrate.close()``,
369+
so ``async with self:`` inside a method would close the caller's connection as
370+
soon as the method returns, breaking every subsequent query on that subtensor.
371+
"""
372+
await self.initialize()
373+
yield
374+
361375
# Helpers ==========================================================================================================
362376

363377
async def _decode_crowdloan_entry(
@@ -7216,7 +7230,7 @@ async def burned_register(
72167230
Notes:
72177231
- Rate Limits: <https://docs.learnbittensor.org/learn/chain-rate-limits#registration-rate-limits>
72187232
"""
7219-
async with self:
7233+
async with self._ensure_connection():
72207234
if netuid == 0:
72217235
return await root_register_extrinsic(
72227236
subtensor=self,
@@ -8507,7 +8521,7 @@ async def register(
85078521
Notes:
85088522
- Rate Limits: <https://docs.learnbittensor.org/learn/chain-rate-limits#registration-rate-limits>
85098523
"""
8510-
async with self:
8524+
async with self._ensure_connection():
85118525
if netuid == 0:
85128526
return await root_register_extrinsic(
85138527
subtensor=self,
@@ -8586,7 +8600,7 @@ async def register_limit(
85868600
- Rate Limits: <https://docs.learnbittensor.org/learn/chain-rate-limits#registration-rate-limits>
85878601
"""
85888602
check_balance_amount(limit_price)
8589-
async with self:
8603+
async with self._ensure_connection():
85908604
return await register_limit_extrinsic(
85918605
subtensor=self,
85928606
wallet=wallet,

tests/unit_tests/test_async_subtensor.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,31 @@ async def test_burned_register(subtensor, fake_wallet, mocker):
194194
)
195195

196196

197+
@pytest.mark.asyncio
198+
async def test_register_family_does_not_close_shared_connection(
199+
subtensor, fake_wallet, mocker
200+
):
201+
"""register/burned_register/register_limit must not close the substrate connection
202+
they share with the caller. The old ``async with self:`` ran ``__aexit__`` ->
203+
``substrate.close()`` on return, breaking every later query on the same subtensor."""
204+
mocker.patch.object(subtensor, "compose_call")
205+
mocker.patch.object(
206+
subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "")
207+
)
208+
mocker.patch.object(
209+
subtensor,
210+
"get_neuron_for_pubkey_and_subnet",
211+
return_value=NeuronInfo.get_null_neuron(),
212+
)
213+
mocker.patch.object(subtensor, "get_balance", return_value=Balance.from_tao(1))
214+
mocker.patch.object(subtensor, "recycle")
215+
216+
await subtensor.burned_register(wallet=fake_wallet, netuid=14)
217+
218+
# the connection the caller is sharing must be left open
219+
subtensor.substrate.close.assert_not_called()
220+
221+
197222
@pytest.mark.asyncio
198223
async def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker):
199224
mock_substrate.submit_extrinsic.return_value = mocker.AsyncMock(

0 commit comments

Comments
 (0)