Skip to content
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

Data stops getting stored to session #2393

Closed
joelanman opened this issue Feb 19, 2024 · 11 comments · Fixed by #2399
Closed

Data stops getting stored to session #2393

joelanman opened this issue Feb 19, 2024 · 11 comments · Fixed by #2399
Assignees
Milestone

Comments

@joelanman
Copy link
Contributor

joelanman commented Feb 19, 2024

Description of the issue

We're getting a few reports that data stops getting stored to session sometimes.
Clearing .tmp/sessions seems to fix
Other reports suggest this happens in Chrome and not other browsers

Steps to reproduce the issue

  • Make a prototype that records and displays data
  • Go through the prototype, it sometimes stops storing data

Actual vs expected behaviour

  • session data should be stored and usable unless cleared

Environment (where applicable)

  • Operating system:
  • Browser:
  • Browser version:
  • GOV.UK Prototype Kit version:
@enoranidi
Copy link

enoranidi commented Feb 19, 2024

I have added a video to show what is happening for me at the moment when I use the latest version of Chrome and the GOV.UK Prototype Kit: https://www.loom.com/share/7ac82f624a3141aba433d9d309bd352d?sid=e8c845f2-0a70-400e-92f3-f93401d7d81d

@enoranidi
Copy link

Operating system: macOS Sonoma
Browser: Chrome
Browser version: 121.0.6167.184 (Official Build) (arm64)
GOV.UK Prototype Kit version: v13.16.0

@StuBamforthDWP
Copy link

Loom video showing my issue with data not being passes through pages. The applicant name and organisation name are inserted into subsequent questions to add context, the applicant name isn't being passed through. Also the details captured in the questions are not being displayed on the Check your answers page.

https://www.loom.com/share/6b33464f6bc346c38b9dfea4835e7779?sid=bf532f85-42e8-41ec-a259-2477d4352b08

@StuBamforthDWP
Copy link

Operating system: macOS Catalina 10.15.7
Browser: Chrome
Browser version: 121.0.6167.184 (Official Build) (x86_64)
GOV.UK Prototype Kit version: v13.16.0

@StuBamforthDWP
Copy link

Clearing .tmp/sessions via the Terminal fixed the issue for me.

Entered:

rm -rf .tmp/sessions

in the VS Code terminal.

Then restarted the app and it's working again.

@HarryMtds
Copy link

I am having the same issue that everyone else is. It affects prototypes running locally and in Heroku.

I've not been able to reproduce the error reliably. It happens randomly when moving from one page, through a route to another page. It forgets everything stored in session and the session object is no longer able to be read from or written too.

Given that clearing all the JSON files in .tmp/sessions fixes the issue- I think that something in there is the cause of the issue. I would go through these and check but given that a new JSON session file is created on every page load, this would take a prohibitive amount of time.

Operating system: MacOS 14.3.1 (23D60)
Browser: Chrome
Browser version: 121.0.6167.184
GOV.UK Prototype Kit version: 13.16.0
Node version: 20.10.0

@joelanman
Copy link
Contributor Author

@HarryMtds thanks, does it happen in other browsers?

@HarryMtds
Copy link

HarryMtds commented Feb 21, 2024

Can't seem to replicate it in Safari. I have noticed an interesting difference in behaviour between Chrome and Safari.

Chrome:

  1. Delete all files in .tmp/sessions
  2. Open localhost:3000
  3. Two JSON files are created in .tmp/sessions The difference between these files is the cookies.expires value

Safari:

  1. Delete all files in .tmp/sessions
  2. Open localhost:3000
  3. One JSON file created in .tmp/sessions

Edit:

On further inspection, chrome generates a new session file on every page load, whereas Safari does not

@joelanman
Copy link
Contributor Author

@HarryMtds super helpful thanks!

@36degrees
Copy link
Contributor

I've been trying to get to the bottom of what's going on here… a few observations so far…

  • the request for the new manifest added in GOV.UK Frontend v5.0 (served at /plugin-assets/govuk-frontend/dist/govuk/assets/manifest.json in the kit) is made without credentials which results in an extra session being created (and never reused) every time the page is loaded. I believe this explains the difference in behaviour between Chrome and Safari described in Data stops getting stored to session #2393 (comment). I don't think this is the root cause of the bug, but I think it might be making it more likely, be increasing the number of sessions that need to be stored.
  • We're setting resave: false. The guidance for resave says 'the best way to know is to check with your store if it implements the touch method. If it does, then you can safely set resave: false. If it does not implement the touch method and your store sets an expiration date on stored sessions, then you likely need resave: true.' As far as I can tell, our session store does not implement touch. But I still don't really understand what resave does and whether this is relevant or not.
  • I'm reasonably sure I've observed expiry dates in the session files being updated with older dates, i.e. the time to their expiry being shortened, but I've not yet worked out why this is happening (or, indeed, if I was imagining it…)

@joelanman
Copy link
Contributor Author

the timing of the issue seems to point to the manifest being a possible cause - we could try asking people who see this issue to try something like this in their app/views/layouts/main.html:

{% block headIcons %}{% endblock %}

36degrees added a commit that referenced this issue Feb 27, 2024
We store session data in the filesystem using our own session `FileStore` class. Each session is stored as a separate JSON file inside `.tmp/sessions` with an expiry date and the data for session.

As part of the `set` method for the FileStore class, we have some logic which tries to clean up expired sessions from the filesystem.

This loops through all of the files in the session store directory and checks each one in turn to see if the expiry date in the JSON is in the past. If it is, it calls `this.destroy` passing the `storedSessionId`. This happens asynchronously – we map over the files in the directory to create an array of promises, each of which awaits a call to the filesystem to read the JSON.

Unfortunately, `storedSessionId` is scoped to the outer `set` method and is therefore affected by the other promises that are running concurrently.

Because of this, when there are expired sessions in the session store, it’s possible for non-expired sessions to be destroyed instead, depending on the value of `storedSessionId` at the time destroy is called.

Furthermore, because this leaves the expired session in place, further attempts to set data are likely to continue cleaning up the non-expired sessions.

You can observe this (and verify that this fixes the issue) by:

1. Adding logging just before the call to this.destroy on line 23:

   ```
   console.log(`Destroying expired session ${storedSessionId}`)
   ```

2. Clearing all session data by deleting `.tmp/session`
3. Generating multiple non-expired sessions by refreshing the Prototype Kit multiple times [^1]
4. Manually updating the expiry date in the JSON for one of the sessions to be in the past [^2]
5. Refreshing the kit and observing that a session with a different ID is destroyed

Fix this by correctly scoping the `storedSessionId` variable to the asynchronous function, preventing it being altered by other promises resolving at the same time.

Due to the limited support we are currently able to provide for the kit, I have made the smallest possible change I believe fixes the bug and not done any further refactoring.

Also, whilst I believe it would be beneficial to have tests that cover this code (including this specific bug) I have not done this as it’s non-trivial and involves mocking the file system.

Closes #2393.

[1]: This works because the request for the new manifest added in GOV.UK Frontend v5.0 (served at `/plugin-assets/govuk-frontend/dist/govuk/assets/manifest.json` in the kit) is made without credentials (see the note on https://web.dev/articles/add-manifest) which results in an extra session being created (and never reused) every time the page is loaded. I think this also might have made this bug more likely to occur because of the sheer number of session files that get created.

[2]: In case it’s useful for testing, this jq command shows the expiry date for all current sessions:

  ```
  jq -r -n 'inputs | "\(input_filename) \(.cookie.expires)"' .tmp/sessions/*.json | sort | head
  ```

Co-authored-by: Colin Rotherham <[email protected]>
@36degrees 36degrees moved this from In progress 📝 to Needs review 🔍 in GOV.UK Design System cycle board Feb 27, 2024
@36degrees 36degrees moved this from Needs review 🔍 to Ready to release 🚀 in GOV.UK Design System cycle board Feb 28, 2024
@36degrees 36degrees moved this from Ready to release 🚀 to Done 🏁 in GOV.UK Design System cycle board Mar 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging a pull request may close this issue.

5 participants