Pass the resubscribe internal in new function and remove unneeded try except

This commit is contained in:
Arnaud 2025-03-30 12:38:50 +02:00
parent 3265595aae
commit fcd4cb8672
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
4 changed files with 56 additions and 56 deletions

View File

@ -15,7 +15,7 @@ import ./conversions
export serde export serde
# Default re-subscription period is 240 seconds (4 minutes) # Default re-subscription period is 240 seconds (4 minutes)
const WsResubscribe {.intdefine.}: int64 = 240 const WsResubscribe {.intdefine.}: int = 240
type type
JsonRpcSubscriptions* = ref object of RootObj JsonRpcSubscriptions* = ref object of RootObj
@ -28,8 +28,6 @@ type
# WebsocketSubscriptions, when using hardhat, subscriptions are dropped after 5 # WebsocketSubscriptions, when using hardhat, subscriptions are dropped after 5
# minutes. # minutes.
logFilters: Table[JsonNode, EventFilter] logFilters: Table[JsonNode, EventFilter]
when defined(ws_resubscribe):
resubscribeFut: Future[void]
MethodHandler* = proc (j: JsonNode) {.gcsafe, raises: [].} MethodHandler* = proc (j: JsonNode) {.gcsafe, raises: [].}
SubscriptionCallback = proc(id: JsonNode, arguments: ?!JsonNode) {.gcsafe, raises:[].} SubscriptionCallback = proc(id: JsonNode, arguments: ?!JsonNode) {.gcsafe, raises:[].}
@ -106,11 +104,18 @@ proc getCallback(subscriptions: JsonRpcSubscriptions,
type type
WebSocketSubscriptions = ref object of JsonRpcSubscriptions WebSocketSubscriptions = ref object of JsonRpcSubscriptions
logFiltersLock: AsyncLock logFiltersLock: AsyncLock
when defined(ws_resubscribe):
resubscribeFut: Future[void]
resubscribeInterval: int
proc new*(_: type JsonRpcSubscriptions, proc new*(_: type JsonRpcSubscriptions,
client: RpcWebSocketClient): JsonRpcSubscriptions = client: RpcWebSocketClient,
resubscribeInterval = WsResubscribe): JsonRpcSubscriptions =
when defined(ws_resubscribe):
let subscriptions = WebSocketSubscriptions(client: client, resubscribeInterval: resubscribeInterval)
else:
let subscriptions = WebSocketSubscriptions(client: client)
let subscriptions = WebSocketSubscriptions(client: client)
proc subscriptionHandler(arguments: JsonNode) {.raises:[].} = proc subscriptionHandler(arguments: JsonNode) {.raises:[].} =
let id = arguments{"subscription"} or newJString("") let id = arguments{"subscription"} or newJString("")
if callback =? subscriptions.getCallback(id): if callback =? subscriptions.getCallback(id):
@ -144,15 +149,11 @@ method subscribeBlocks(subscriptions: WebSocketSubscriptions,
let res = Block.fromJson(arguments{"result"}).mapFailure(SubscriptionError) let res = Block.fromJson(arguments{"result"}).mapFailure(SubscriptionError)
onBlock(res) onBlock(res)
try: convertErrorsToSubscriptionError:
withLock(subscriptions): withLock(subscriptions):
convertErrorsToSubscriptionError: let id = await subscriptions.client.eth_subscribe("newHeads")
let id = await subscriptions.client.eth_subscribe("newHeads") subscriptions.callbacks[id] = callback
subscriptions.callbacks[id] = callback return id
return id
except AsyncLockError as e:
error "Lock error when trying to subscribe to blocks", err = e.msg
raise newException(SubscriptionError, "Cannot subscribe to the blocks because of lock error")
method subscribeLogs(subscriptions: WebSocketSubscriptions, method subscribeLogs(subscriptions: WebSocketSubscriptions,
filter: EventFilter, filter: EventFilter,
@ -167,16 +168,12 @@ method subscribeLogs(subscriptions: WebSocketSubscriptions,
let res = Log.fromJson(arguments{"result"}).mapFailure(SubscriptionError) let res = Log.fromJson(arguments{"result"}).mapFailure(SubscriptionError)
onLog(res) onLog(res)
try: convertErrorsToSubscriptionError:
withLock(subscriptions): withLock(subscriptions):
convertErrorsToSubscriptionError: let id = await subscriptions.client.eth_subscribe("logs", filter)
let id = await subscriptions.client.eth_subscribe("logs", filter) subscriptions.callbacks[id] = callback
subscriptions.callbacks[id] = callback subscriptions.logFilters[id] = filter
subscriptions.logFilters[id] = filter return id
return id
except AsyncLockError as e:
error "Lock error when trying to subscribe to logs", err = e.msg
raise newException(SubscriptionError, "Cannot subscribe to the logs because of lock error")
method unsubscribe*(subscriptions: WebSocketSubscriptions, method unsubscribe*(subscriptions: WebSocketSubscriptions,
id: JsonNode) id: JsonNode)
@ -191,7 +188,7 @@ method unsubscribe*(subscriptions: WebSocketSubscriptions,
# Ignore if uninstallation of the subscribiton fails. # Ignore if uninstallation of the subscribiton fails.
discard discard
method close*(subscriptions: WebsocketSubscriptions) {.async.} = method close*(subscriptions: WebSocketSubscriptions) {.async: (raises: [CancelledError, SubscriptionError]).} =
await procCall JsonRpcSubscriptions(subscriptions).close() await procCall JsonRpcSubscriptions(subscriptions).close()
when defined(ws_resubscribe): when defined(ws_resubscribe):
if not subscriptions.resubscribeFut.isNil: if not subscriptions.resubscribeFut.isNil:
@ -201,28 +198,31 @@ when defined(ws_resubscribe):
# This is a workaround to manage the 5 minutes limit due to hardhat. # This is a workaround to manage the 5 minutes limit due to hardhat.
# See https://github.com/NomicFoundation/hardhat/issues/2053#issuecomment-1061374064 # See https://github.com/NomicFoundation/hardhat/issues/2053#issuecomment-1061374064
proc resubscribeWebsocketEventsOnTimeout*(subscriptions: WebsocketSubscriptions) {.async: (raises: [CancelledError]).} = proc resubscribeWebsocketEventsOnTimeout*(subscriptions: WebsocketSubscriptions) {.async: (raises: [CancelledError]).} =
while true: if subscriptions.resubscribeInterval <= 0:
await sleepAsync(WsResubscribe.seconds) info "Skipping the resubscription because the interval is zero or negative", period = subscriptions.resubscribeInterval
try: else:
withLock(subscriptions): while true:
for id, callback in subscriptions.callbacks: await sleepAsync(subscriptions.resubscribeInterval.seconds)
try:
withLock(subscriptions):
for id, callback in subscriptions.callbacks:
var newId: JsonNode var newId: JsonNode
if id in subscriptions.logFilters: if id in subscriptions.logFilters:
let filter = subscriptions.logFilters[id] let filter = subscriptions.logFilters[id]
newId = await subscriptions.client.eth_subscribe("logs", filter) newId = await subscriptions.client.eth_subscribe("logs", filter)
subscriptions.logFilters[newId] = filter subscriptions.logFilters[newId] = filter
subscriptions.logFilters.del(id) subscriptions.logFilters.del(id)
else: else:
newId = await subscriptions.client.eth_subscribe("newHeads") newId = await subscriptions.client.eth_subscribe("newHeads")
subscriptions.callbacks[newId] = callback subscriptions.callbacks[newId] = callback
subscriptions.callbacks.del(id) subscriptions.callbacks.del(id)
discard await subscriptions.client.eth_unsubscribe(id) discard await subscriptions.client.eth_unsubscribe(id)
except CancelledError as e: except CancelledError as e:
raise e raise e
except CatchableError as e: except CatchableError as e:
error "WS resubscription failed" , error = e.msg error "WS resubscription failed" , error = e.msg
# Polling # Polling

View File

@ -13,11 +13,13 @@ suite "Websocket re-subscriptions":
var subscriptions: JsonRpcSubscriptions var subscriptions: JsonRpcSubscriptions
var client: RpcWebSocketClient var client: RpcWebSocketClient
var resubscribeInterval: int
setup: setup:
resubscribeInterval = 3
client = newRpcWebSocketClient() client = newRpcWebSocketClient()
await client.connect("ws://" & getEnv("ETHERS_TEST_PROVIDER", "localhost:8545")) await client.connect("ws://" & getEnv("ETHERS_TEST_PROVIDER", "localhost:8545"))
subscriptions = JsonRpcSubscriptions.new(client) subscriptions = JsonRpcSubscriptions.new(client, resubscribeInterval = resubscribeInterval)
subscriptions.start() subscriptions.start()
teardown: teardown:
@ -28,15 +30,14 @@ suite "Websocket re-subscriptions":
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example]) let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
let emptyHandler = proc(log: ?!Log) = discard let emptyHandler = proc(log: ?!Log) = discard
let subscription = await subscriptions.subscribeLogs(filter, emptyHandler) for i in 1..10:
discard await subscriptions.subscribeLogs(filter, emptyHandler)
# Wait until the re-subscription starts # Wait until the re-subscription starts
await sleepAsync(3.int64.seconds) await sleepAsync(resubscribeInterval.seconds)
try: # Attempt to modify callbacks while its being iterated
await subscriptions.unsubscribe(subscription) discard await subscriptions.subscribeLogs(filter, emptyHandler)
except CatchableError:
fail()
test "resubscribe events take effect with new subscription IDs in the log filters": test "resubscribe events take effect with new subscription IDs in the log filters":
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example]) let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
@ -46,7 +47,8 @@ suite "Websocket re-subscriptions":
check id in subscriptions.logFilters check id in subscriptions.logFilters
check subscriptions.logFilters.len == 1 check subscriptions.logFilters.len == 1
await sleepAsync(4.int64.seconds) # Make sure the subscription is done
await sleepAsync((resubscribeInterval + 1).seconds)
# The previous subscription should not be in the log filters # The previous subscription should not be in the log filters
check not (id in subscriptions.logFilters) check not (id in subscriptions.logFilters)

View File

@ -1,6 +1,8 @@
import ./jsonrpc/testJsonRpcProvider import ./jsonrpc/testJsonRpcProvider
import ./jsonrpc/testJsonRpcSigner import ./jsonrpc/testJsonRpcSigner
import ./jsonrpc/testJsonRpcSubscriptions import ./jsonrpc/testJsonRpcSubscriptions
when defined(ws_resubscribe):
import ./jsonrpc/testWsResubscription
import ./jsonrpc/testConversions import ./jsonrpc/testConversions
import ./jsonrpc/testErrors import ./jsonrpc/testErrors

View File

@ -7,8 +7,4 @@ requires "asynctest >= 0.4.0 & < 0.5.0"
task test, "Run the test suite": task test, "Run the test suite":
exec "nimble install -d -y" exec "nimble install -d -y"
exec "nim c -r test" exec "nim c --define:ws_resubscribe=0 -r test"
task testWsResubscription, "Run the test suite":
exec "nimble install -d -y"
exec "nim c --define:ws_resubscribe=3 -r providers/jsonrpc/testWsResubscription"