-
Notifications
You must be signed in to change notification settings - Fork 956
Resist session race conditions using reference counting #1713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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
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.
This seems to break sessions that are primitive values., say S
is string
.
Ah telegraf cannot even do this so nvm |
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.
This is quite complex, I'll need to re-read it before approving
* move debug to top-level * don't check if cached is truthy, it must exist
* move debug to top-level * don't check if cached is truthy, it must exist
3e6a4e0
to
b5c2986
Compare
* move debug to top-level * don't check if cached is truthy, it must exist
4958d39
to
7424224
Compare
* move debug to top-level * don't check if cached is truthy, it must exist
5a7382f
to
74a5339
Compare
This is not a breaking change because it was never said anywhere that sessions could be strings 😁 |
Just to clarify, this is only a type-level constraint that has already existed on Telegraf, and is useful because scenes need session to be an object. However, since this PR, it is not a technical requirement, and sessions can be primitive. After discussing with @wojpawlik, we decided to leave the constraint be until someone asks for it. |
* move debug to top-level * don't check if cached is truthy, it must exist
c9bc6be
to
e4dfb70
Compare
FINALLY! |
Fixes #1372, fixes #1695, fixes #1799
Supersedes #1436
This is a very involved, but well-commented solution. Short explanation follows.
Edit: This PR now also introduces
defaultSession
as an optional parameter tosession({})
. Unit tests have been updated.Current state:
Solution:
Problems:
If an update is terminated halfway (because it threw an error), any changes it already made are not rolled back in the memory cache. If other concurrent requests are using the reference, they will see the updated object, and if they complete processing successfully, the updated session will persist! We could make this behaviour more predictable by also persisting session in the error catch branch.This behaviour has been changed so session is always written back, regardless of whether middleware chain completes or errors. This means assigning to session is always almost as good as having written to store.
getSessionKey
is async in v4, so it still allows some racing, but provided solution automatically corrects for this by ignoring duplicate store fetches if a reference is already cached when they return. In v5,getSessionKey
should probably be made synchronous—counter-arguments welcome.SharedArrayBuffer
andAtomics
. Will explore this if there is demand for such a thing.getSessionKey :: ctx -> ctx.chat.id
for webhooks. Telegram ensures that webhook updates are serial for a given chatId already!This is not true; since this PR, session can be a primitive, but we're leaving the type-level constraint in place until someone asks for it.session
has to be an object for references to be shared. It cannot be a primitive value. This is already true for Telegraf, because Scenes and Wizards cannot work otherwise.If there are concerns about this solution subtly changing the behaviour of existing deprecated
session
, particularly in multi-process situations, I'm open to discussion about splitting this into an external module at@telegraf/session
which users can opt-in to, with the naive implementation as the default.However, I don't like the idea of permanently moving session out of telegraf core as previously discussed in #1436, because other core (Scenes and Wizards) and thirdparty modules inherently rely on sessions to exist.
Testing
The original reproduction code posted in #1372 works as expected with the proposed version of session. Try it yourself by installing this branch:
npm i "telegraf/telegraf#feat-better-session"
Further, unit tests have been added with synchronous and asynchronous session stores. They appear rock-solid, but if anyone wants to produce a breaking test, I'd be happy to fix the implementation to conform.