-
Notifications
You must be signed in to change notification settings - Fork 9
fix: smoldot connection race condition #671
Conversation
64d64e8
to
488a1f1
Compare
69b9728
to
eed09b4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
delete this.subscriptionHandlers[subscriptionId] | ||
this.send(this.nextId++, unsubscribe, [subscriptionId]) | ||
}) | ||
const id = this.nextId++ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why were these changes to subscription reverted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kratico found a bug in which the subscription handler is not set before the first message is received. Was specific to Smoldot if I'm not mistaken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@harrysolovay @tjjfvi this is surfaced with smoldot because it Connection.handle
is invoked before the subscription handler is set.
It may happen with WebSockets too but, most of the time, the first subscription message in WS is not very fast as in smoldot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, does smoldot send multiple messages synchronously? In that case, it wouldn't be a question of speed, just the microtask from the await
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that for this particular subscription, the chainHead_unstable_follow
responds with 2 messages very quickly
- the response for the subscription id
- the response for the first subscription message
It's a microtask race condition
For the above subscription, this smoldot loop run 2 times
while (true) {
try {
const response = await Promise.race([
this.listening,
chain.nextJsonRpcResponse(),
])
if (!response) break
this.handle(JSON.parse(response))
} catch (_e) {}
}
before this connection.subscription
const message = await this.#call(id, subscribe, params)
if (signal.aborted) {
delete this.subscriptionPendingInits[id]
return
}
if (message.error) {
delete this.subscriptionPendingInits[id]
return handler(message)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, I believe this is because deferred.resolve
uses two microtask ticks (first to resolve the argument, then to resolve the real promise). I plan to report this to deno std (this, along with another related issue with deferred), but this is a good solution for now.
Connection.handle
was invoked before the subscription handler was set)