Found while fixing #737. Two teardown races leave a server-side query subscription running after the client has dropped it.
1. qu during an in-flight qs
Agent._queryUnsubscribe only destroys an emitter that's already in agent.subscribedQueries. A qs only registers its emitter after the 'query' middleware and the pub/sub subscribe have finished, and on reconnect also after the known-results reads (#737). A qu that arrives during that window finds nothing to destroy. The emitter is registered anyway and keeps polling and sending queryUpdates until the client disconnects.
var query = connection.createSubscribeQuery('dogs', {})
query.destroy() straight away
agent.subscribedQueries still holds the query.
2. Destroying a QueryEmitter mid-poll doesn't stop it
QueryEmitter.destroy() clears the poll timers, but a poll that's already in flight still ends in _finishPoll → _flushPoll, which schedules a fresh interval poll. Nothing ever clears that timer. With pollDebounce: 0 and pollInterval > 0, the emitter keeps polling the database forever after a qu, or after Agent._cleanup on disconnect. Clients can choose both values (#734).
var query = connection.createSubscribeQuery('dogs', {}, {pollDebounce: 0, pollInterval: 10})
- While a
db.queryPoll call is outstanding, call query.destroy() and connection.close()
db.queryPoll keeps being called every ~10ms, indefinitely.
Possible fixes
- Make
QueryEmitter.destroy() final: set a flag that _flushPoll checks.
- Track in-flight
qs requests per query id, so a qu or a disconnect can cancel them.
Found while fixing #737. Two teardown races leave a server-side query subscription running after the client has dropped it.
1.
quduring an in-flightqsAgent._queryUnsubscribeonly destroys an emitter that's already inagent.subscribedQueries. Aqsonly registers its emitter after the'query'middleware and the pub/sub subscribe have finished, and on reconnect also after the known-results reads (#737). Aquthat arrives during that window finds nothing to destroy. The emitter is registered anyway and keeps polling and sendingqueryUpdates until the client disconnects.var query = connection.createSubscribeQuery('dogs', {})query.destroy()straight awayagent.subscribedQueriesstill holds the query.2. Destroying a
QueryEmittermid-poll doesn't stop itQueryEmitter.destroy()clears the poll timers, but a poll that's already in flight still ends in_finishPoll→_flushPoll, which schedules a fresh interval poll. Nothing ever clears that timer. WithpollDebounce: 0andpollInterval > 0, the emitter keeps polling the database forever after aqu, or afterAgent._cleanupon disconnect. Clients can choose both values (#734).var query = connection.createSubscribeQuery('dogs', {}, {pollDebounce: 0, pollInterval: 10})db.queryPollcall is outstanding, callquery.destroy()andconnection.close()db.queryPollkeeps being called every ~10ms, indefinitely.Possible fixes
QueryEmitter.destroy()final: set a flag that_flushPollchecks.qsrequests per query id, so aquor a disconnect can cancel them.