resubscribe when error in polling
This commit is contained in:
parent
5a9895b792
commit
c6a59b5187
|
@ -152,47 +152,62 @@ proc new*(_: type JsonRpcSubscriptions,
|
||||||
|
|
||||||
let subscriptions = PollingSubscriptions(client: client)
|
let subscriptions = PollingSubscriptions(client: client)
|
||||||
|
|
||||||
proc getChanges(originalId: JsonNode): Future[JsonNode] {.async.} =
|
proc resubscribe(id: JsonNode) {.async: (raises: [CancelledError]).} =
|
||||||
try:
|
try:
|
||||||
let mappedId = subscriptions.subscriptionMapping[originalId]
|
|
||||||
let changes = await subscriptions.client.eth_getFilterChanges(mappedId)
|
|
||||||
if changes.kind == JNull:
|
|
||||||
return newJArray()
|
|
||||||
elif changes.kind != JArray:
|
|
||||||
raise newException(SubscriptionError,
|
|
||||||
"HTTP polling: unexpected value returned from eth_getFilterChanges." &
|
|
||||||
" Expected: JArray, got: " & $changes.kind)
|
|
||||||
return changes
|
|
||||||
except CancelledError as e:
|
|
||||||
raise e
|
|
||||||
except CatchableError as e:
|
|
||||||
if "filter not found" in e.msg:
|
|
||||||
var newId: JsonNode
|
var newId: JsonNode
|
||||||
# Log filters are stored in logFilters, block filters are not persisted
|
# Log filters are stored in logFilters, block filters are not persisted
|
||||||
# there is they do not need any specific data for their recreation.
|
# there is they do not need any specific data for their recreation.
|
||||||
# We use this to determine if the filter was log or block filter here.
|
# We use this to determine if the filter was log or block filter here.
|
||||||
if subscriptions.logFilters.hasKey(originalId):
|
if subscriptions.logFilters.hasKey(id):
|
||||||
let filter = subscriptions.logFilters[originalId]
|
let filter = subscriptions.logFilters[id]
|
||||||
newId = await subscriptions.client.eth_newFilter(filter)
|
newId = await subscriptions.client.eth_newFilter(filter)
|
||||||
else:
|
else:
|
||||||
newId = await subscriptions.client.eth_newBlockFilter()
|
newId = await subscriptions.client.eth_newBlockFilter()
|
||||||
subscriptions.subscriptionMapping[originalId] = newId
|
subscriptions.subscriptionMapping[id] = newId
|
||||||
return await getChanges(originalId)
|
except CancelledError as error:
|
||||||
else:
|
raise error
|
||||||
raise e
|
except CatchableError:
|
||||||
|
# there's nothing further we can do here
|
||||||
|
discard
|
||||||
|
|
||||||
proc poll(id: JsonNode) {.async.} =
|
proc getChanges(id: JsonNode): Future[JsonNode] {.async: (raises: [CancelledError]).} =
|
||||||
|
try:
|
||||||
|
let mappedId = subscriptions.subscriptionMapping[id]
|
||||||
|
let changes = await subscriptions.client.eth_getFilterChanges(mappedId)
|
||||||
|
if changes.kind == JArray:
|
||||||
|
return changes
|
||||||
|
except KeyError as error:
|
||||||
|
raiseAssert "subscription mapping invalid: " & error.msg
|
||||||
|
except JsonRpcError:
|
||||||
|
await resubscribe(id)
|
||||||
|
# TODO: we could still miss some events between losing the subscription
|
||||||
|
# and resubscribing. We should probably adopt a strategy like ethers.js,
|
||||||
|
# whereby we keep track of the latest block number that we've seen
|
||||||
|
# filter changes for:
|
||||||
|
# https://github.com/ethers-io/ethers.js/blob/f97b92bbb1bde22fcc44100af78d7f31602863ab/packages/providers/src.ts/base-provider.ts#L977
|
||||||
|
except CancelledError as error:
|
||||||
|
raise error
|
||||||
|
except CatchableError:
|
||||||
|
# there's nothing we can do here
|
||||||
|
discard
|
||||||
|
return newJArray()
|
||||||
|
|
||||||
|
proc poll(id: JsonNode) {.async: (raises: [CancelledError]).} =
|
||||||
for change in await getChanges(id):
|
for change in await getChanges(id):
|
||||||
if callback =? subscriptions.getCallback(id):
|
if callback =? subscriptions.getCallback(id):
|
||||||
callback(id, change)
|
callback(id, change)
|
||||||
|
|
||||||
proc poll {.async.} =
|
proc poll {.async: (raises: []).} =
|
||||||
untilCancelled:
|
try:
|
||||||
|
while true:
|
||||||
for id in toSeq subscriptions.callbacks.keys:
|
for id in toSeq subscriptions.callbacks.keys:
|
||||||
await poll(id)
|
await poll(id)
|
||||||
await sleepAsync(pollingInterval)
|
await sleepAsync(pollingInterval)
|
||||||
|
except CancelledError:
|
||||||
|
discard
|
||||||
|
|
||||||
subscriptions.polling = poll()
|
subscriptions.polling = poll()
|
||||||
|
asyncSpawn subscriptions.polling
|
||||||
subscriptions
|
subscriptions
|
||||||
|
|
||||||
method close*(subscriptions: PollingSubscriptions) {.async.} =
|
method close*(subscriptions: PollingSubscriptions) {.async.} =
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import pkg/serde
|
|
||||||
import std/os
|
import std/os
|
||||||
import std/sequtils
|
import std/sequtils
|
||||||
import std/importutils
|
import std/importutils
|
||||||
|
@ -106,13 +105,18 @@ suite "HTTP polling subscriptions - filter not found":
|
||||||
|
|
||||||
privateAccess(PollingSubscriptions)
|
privateAccess(PollingSubscriptions)
|
||||||
|
|
||||||
setup:
|
proc startServer() {.async.} =
|
||||||
mockServer = MockRpcHttpServer.new()
|
mockServer = MockRpcHttpServer.new()
|
||||||
mockServer.start()
|
mockServer.start()
|
||||||
|
|
||||||
client = newRpcHttpClient()
|
|
||||||
await client.connect("http://" & $mockServer.localAddress()[0])
|
await client.connect("http://" & $mockServer.localAddress()[0])
|
||||||
|
|
||||||
|
proc stopServer() {.async.} =
|
||||||
|
await mockServer.stop()
|
||||||
|
|
||||||
|
setup:
|
||||||
|
client = newRpcHttpClient()
|
||||||
|
await startServer()
|
||||||
|
|
||||||
subscriptions = PollingSubscriptions(
|
subscriptions = PollingSubscriptions(
|
||||||
JsonRpcSubscriptions.new(
|
JsonRpcSubscriptions.new(
|
||||||
client,
|
client,
|
||||||
|
@ -174,3 +178,16 @@ suite "HTTP polling subscriptions - filter not found":
|
||||||
await subscriptions.unsubscribe(id)
|
await subscriptions.unsubscribe(id)
|
||||||
|
|
||||||
check not subscriptions.subscriptionMapping.hasKey id
|
check not subscriptions.subscriptionMapping.hasKey id
|
||||||
|
|
||||||
|
test "polling continues with new filter after temporary error":
|
||||||
|
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||||
|
let emptyHandler = proc(log: Log) = discard
|
||||||
|
|
||||||
|
let id = await subscriptions.subscribeLogs(filter, emptyHandler)
|
||||||
|
|
||||||
|
await stopServer()
|
||||||
|
mockServer.invalidateFilter(id)
|
||||||
|
await sleepAsync(50.milliseconds)
|
||||||
|
await startServer()
|
||||||
|
|
||||||
|
check eventually subscriptions.subscriptionMapping[id] != id
|
||||||
|
|
Loading…
Reference in New Issue