Skip to content

dup course id fix #155#164

Merged
DAC098 merged 3 commits intomainfrom
t155_dup_course_ids
Apr 10, 2026
Merged

dup course id fix #155#164
DAC098 merged 3 commits intomainfrom
t155_dup_course_ids

Conversation

@DAC098
Copy link
Copy Markdown
Contributor

@DAC098 DAC098 commented Mar 27, 2026

there is an issue with how the course ids are being generated that is causing duplicates to be generated consistently thus preventing the creation of new courses for the site. I detailed more of this in api/src/utils/id_generator.ts. for now this will keep track of created course ids and pre-existing ones so that we can relyably create new ones without having to hit the database all the time and prevent potential race conditions by having each request calculate it.

fixes #155

there is an issue with how the course ids are being generated that is
causing duplicates to be generated consistently thus preventing the
creation of new courses for the site. I detailed more of this in
`api/src/utils/id_generator.ts`. for now this will keep track of created
course ids and pre-existing ones so that we can relyably create new ones
without having to hit the database all the time and prevent potential
race conditions by having each request calculate it.
@DAC098 DAC098 requested a review from NickK21 March 27, 2026 17:28
fixes a type error, not sure where the type is so that it can be
specified so it is being inferred.
Comment on lines +194 to +200
for (let key of Object.keys(tmp_ids)) {
if (now - tmp_ids[key].ts > max_lifetime) {
ids_index.delete(tmp_ids[key].id);
tmp_ids.delete(key);
deleted += 1;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tmp_ids is a Map, but this code is iterating it like a plain object. Object.keys(tmp_ids) returns [], so cleanup never runs and stale temporary reservations never expire, so leaked IDs stay blocked for the lifetime of the process.

Can we iterate the map entries directly instead, e.g. for (const [key, tmp] of tmp_ids)?

Running:

node -e "const tmp_ids=new Map([['course-a',{id:1,ts:Date.now()-60000}]]); console.log('Object.keys(tmp_ids)=', Object.keys(tmp_ids)); console.log('Map entries=', [...tmp_ids.entries()]);"

Produces:

Object.keys(tmp_ids)= []
Map entries= [ [ 'course-a', { id: 1, ts: 1775528671920 } ] ]

which shows that while the Map entries does indeed contain course-a, but Object.keys(...) cannot see the items stored inside the Map.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what I get for making quick changes. it should be iterating through the tmp_ids properly

let id = 0;

try {
let result = sequelize.transaction(async (transaction) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This starts the transaction but does not wait for it to finish. As a result, commit_id(id) below can run while id is still 0, and before Sequelize has committed the transaction. If the transaction later rejects, the surrounding catch also won’t reliably perform the intended rollback. Can we await the transaction result before finalizing the generated id?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in the missing await for the return of the transaction.

- when creating a new course, the promise was not being `await`ed and
  the id was thusly not being commited
- changed how the tmp id cleanup was handled to use the built-in
  iterator to fix an issue with not properly iterating over the temp ids
- updated the log for the tmp id cleanup to output how many ids it
  cleaned up
- removed the return from `add_name` as it is not used and would return
  the `Set` which is not desired.
@DAC098 DAC098 requested a review from NickK21 April 8, 2026 00:30
Copy link
Copy Markdown
Contributor

@NickK21 NickK21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean_tmp() can evict a reservation that belongs to a still-running transaction if course creation takes longer than the 30s timeout, however unlikely that may be. In that case commit_id(id) returns false and the in-memory ID set can fall out of sync with the DB until restart/resync.

Not a merge blocker since the DB primary key still protects correctness, but it might be worth logging/resyncing when/if commit_id(id) fails.

@DAC098
Copy link
Copy Markdown
Contributor Author

DAC098 commented Apr 10, 2026

The request to create a course should not be taking 30 seconds but yeah that can still happen. We can bump the time if needed but for now it should be fine.

@DAC098 DAC098 merged commit b5b957e into main Apr 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

generating duplicate course id's

2 participants