Prevent race condition when using edit forms in GUI #7630

Closed
opened 2025-12-29 20:26:15 +01:00 by adam · 5 comments
Owner

Originally created by @tyler-8 on GitHub (Feb 10, 2023).

Originally assigned to: @jeremystretch on GitHub.

NetBox version

v3.4.4

Feature type

Change to existing functionality

Proposed functionality

In edit forms for primary models, when confirming/saving your changes, add a new validation check that the last_updated time has not changed from when you first started editing the object.

If the last_updated time is captured at the time the edit form is loaded, and check again when the Save button is clicked, we can check that the timestamp is the same as before, before finally saving the new change. Otherwise, a validation error is raised.

Use case

It's possible for two users to be editing an object in the GUI at the same time and overwrite each other's changes. Here's the scenario:

  1. Bob creates a Site.
  2. Joe clicks “Edit” on that Site and has the Edit Site form open.
  3. Bob also clicks Edit on the same Site and makes some changes, clicks “Save”.
  4. Joe make changes on his Edit Form and clicks save.
  5. Bob’s changes are no longer present, and Joe’s change is - based on outdated data.

Database changes

N/A

External dependencies

N/A

Originally created by @tyler-8 on GitHub (Feb 10, 2023). Originally assigned to: @jeremystretch on GitHub. ### NetBox version v3.4.4 ### Feature type Change to existing functionality ### Proposed functionality In edit forms for primary models, when confirming/saving your changes, add a new validation check that the `last_updated` time has not changed from when you first started editing the object. If the `last_updated` time is captured at the time the edit form is loaded, and check again when the `Save` button is clicked, we can check that the timestamp is the same as before, before finally saving the new change. Otherwise, a validation error is raised. ### Use case It's possible for two users to be editing an object in the GUI at the same time and overwrite each other's changes. Here's the scenario: 1. Bob creates a Site. 2. Joe clicks “Edit” on that Site and has the Edit Site form open. 3. Bob also clicks Edit on the same Site and makes some changes, clicks “Save”. 4. Joe make changes on his Edit Form and clicks save. 5. Bob’s changes are no longer present, and Joe’s change is - based on outdated data. ### Database changes N/A ### External dependencies N/A
adam added the status: acceptedtype: feature labels 2025-12-29 20:26:15 +01:00
adam closed this issue 2025-12-29 20:26:15 +01:00
Author
Owner

@jeremystretch commented on GitHub (Feb 11, 2023):

Typical Joe. I've been saying we ought to fire that guy.

This is a great idea, and we should be able to implement it pretty seamlessly for (nearly?) every model form.

@jeremystretch commented on GitHub (Feb 11, 2023): Typical Joe. I've been saying we ought to fire that guy. This is a great idea, and we should be able to implement it pretty seamlessly for (nearly?) every model form.
Author
Owner

@sleepinggenius2 commented on GitHub (Feb 13, 2023):

For APIs, I've used the ETag/If-Match header method to support this in the past. For server-rendered pages, I suspect you could use something similar via form fields. Ideally, a similar method would be used for changes made via UI, REST API, and GraphQL (if that supports mutation).

@sleepinggenius2 commented on GitHub (Feb 13, 2023): For APIs, I've used the ETag/If-Match header method to support this in the past. For server-rendered pages, I suspect you could use something similar via form fields. Ideally, a similar method would be used for changes made via UI, REST API, and GraphQL (if that supports mutation).
Author
Owner

@tyler-8 commented on GitHub (Feb 16, 2023):

For APIs, I've used the ETag/If-Match header method to support this in the past. For server-rendered pages, I suspect you could use something similar via form fields. Ideally, a similar method would be used for changes made via UI, REST API, and GraphQL (if that supports mutation).

I'd be hesitant to have this validation (using this particular logic) happen in the API. The additional query to lookup the "in between" last_updated value would likely slow down response times (which would be compounded in high request volume environments). In the GUI you're working at "human speed" so the likelihood of the issue manifesting goes up, but API work tends to have very fast turn around.

@tyler-8 commented on GitHub (Feb 16, 2023): > For APIs, I've used the ETag/If-Match header method to support this in the past. For server-rendered pages, I suspect you could use something similar via form fields. Ideally, a similar method would be used for changes made via UI, REST API, and GraphQL (if that supports mutation). I'd be hesitant to have this validation (using this particular logic) happen in the API. The additional query to lookup the "in between" `last_updated` value would likely slow down response times (which would be compounded in high request volume environments). In the GUI you're working at "human speed" so the likelihood of the issue manifesting goes up, but API work tends to have very fast turn around.
Author
Owner

@sleepinggenius2 commented on GitHub (Feb 16, 2023):

My previous implementation was using the API for a SPA UI, so it already had to make a request to load the object for the view anyway. Depending on how the REST API is implemented here, you should be able to just issue a HEAD request to get the ETag, without having to retrieve the full resource. Those values could potentially leverage the redis cache, which should make the extra request negligible. However, if you are using only the API to update resources, then you should only need to grab the value once, then cache the ETag from the response each time a change is made, to use in the next request. If you are expecting a mix of UI and API changes on the same resource, then you're just going to run into the same potential for a race condition if you don't use the same safeguard in both places. It would also be dangerous in that circumstance to not already be retrieving the resource to validate current state before making the change via the API. This solution should actually be faster in that scenario, because if your request succeeds with the cached ETag value, then there is no need to make that additional request, as the current state has already been validated.

@sleepinggenius2 commented on GitHub (Feb 16, 2023): My previous implementation was using the API for a SPA UI, so it already had to make a request to load the object for the view anyway. Depending on how the REST API is implemented here, you should be able to just issue a HEAD request to get the ETag, without having to retrieve the full resource. Those values could potentially leverage the redis cache, which should make the extra request negligible. However, if you are using only the API to update resources, then you should only need to grab the value once, then cache the ETag from the response each time a change is made, to use in the next request. If you are expecting a mix of UI and API changes on the same resource, then you're just going to run into the same potential for a race condition if you don't use the same safeguard in both places. It would also be dangerous in that circumstance to not already be retrieving the resource to validate current state before making the change via the API. This solution should actually be faster in that scenario, because if your request succeeds with the cached ETag value, then there is no need to make that additional request, as the current state has already been validated.
Author
Owner

@github-actions[bot] commented on GitHub (Aug 1, 2023):

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. Do not attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our contributing guide.

@github-actions[bot] commented on GitHub (Aug 1, 2023): This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#7630