fix: replace ReadWriteLock with NSLock for consistent thread-safety - #476
Conversation
043ca0b to
d6a6c47
Compare
d6a6c47 to
88e6244
Compare
| do { | ||
| let filename = "\(Date().timeIntervalSince1970)" | ||
| try contents.write(to: queue.appendingPathComponent(filename)) | ||
| items.append(filename) |
There was a problem hiding this comment.
pop method is only called when PostHogQueue acquires isFlushingLock
PostHogQueue calls PostHogFileBackedQueue add and delete in the same method
we also call peek/clear from PostHogQueue
maybe a single lock for all those operations within the PostHogQueue would be better?
i'll let @ioannisj figure this out
but i do see some possible race conditions, good catch @Kilo-Loco
There was a problem hiding this comment.
Pushed additional concurrency tests that expose a race condition in the queue. when multiple threads call add() simultaneously, they can generate identical filenames since Date().timeIntervalSince1970 and file operations are not atomic.
To clarify, this was always there and not added with this PR, but since we are working on this we should address it.
There was a problem hiding this comment.
So one operation can overwrite the file of another add operation which can lead to data loss, if my rationale is correct here
|
Hey @Kilo-Loco, thank you for this. I think what we need to do is remove the dependance on Having said that, I can only see this as problematic since the rest of the operations should be protected by |
|
Got it. I can remove the dependencies on |
Hey @Kilo-Loco I think we can remove ReadWriteLock all-together, agreed. It's better we keep locking consistent across the code and this is the only one that's sticking out. I'd say this can be done in this PR, just update title and body. We just migrated to changeset based release process so make sure to merge main |
- Remove @ReadWriteLock property wrapper in favor of explicit NSLock - Update PostHogFileBackedQueue to use NSLock for items array - Update PostHogContext to use NSLock for screenSize - Delete ReadWriteLock.swift (no longer used) This provides clearer intent and consistency with the rest of the codebase.
b838ce8 to
10d65c2
Compare
Let me know if anything needs adjusting. |
ioannisj
left a comment
There was a problem hiding this comment.
LG, left a nit comment
Let's add a changeset and label this PR and we are good to go I think
| screenSizeLock.lock() | ||
| let currentScreenSize = screenSize | ||
| screenSizeLock.unlock() |
There was a problem hiding this comment.
nit: prefer .withLock which has a generic return type. Commenting just here but it can be replaced in multiple places
| screenSizeLock.lock() | |
| let currentScreenSize = screenSize | |
| screenSizeLock.unlock() | |
| let currentScreenSize = screenSizeLock.withLock { screenSize } |
There was a problem hiding this comment.
We switched to changeset based releases. So merge main and pnpm changeset + label this PR with release instead (details in RELEASING.md)
- Replace manual lock()/unlock() with .withLock closures - Add changeset for release
Ready to be labeled with release if everything else looks good 👍🏽 |
…ue-thread-safety # Conflicts: # CHANGELOG.md
|
@marandaneto I'll need your eyes on 2a443ab please. Addressing this comment here. This should be backward compatible with old filenames so no migration needed |
| func add(_ contents: Data) { | ||
| do { | ||
| let filename = "\(Date().timeIntervalSince1970)" | ||
| let filename = "\(Date().timeIntervalSince1970)-\(UUID.v7().uuidString)" |
There was a problem hiding this comment.
can we use only (UUID.v7().uuidString instead?
uuid v7 already has a timestamp in its string
you can also list files from a directory and sort by timestamp from its metadata (we do that on android btw)
There was a problem hiding this comment.
The idea here was to be compatible when mixed with old file names. v7 is sortable which is very helpful, but I'll have to think how to handle the mix of UUIDv7 and old timestamps. I'll think about it
There was a problem hiding this comment.
you can also list files from a directory and sort by timestamp from its metadata (we do that on android btw)
yeah probably this is the way
There was a problem hiding this comment.
i just mentioned above how we do this for android
this should be compatible with whatever name the files are
see example https://github.com/PostHog/posthog-android/blob/65700f9159a789861b35dac1f936f76bb0683807/posthog/src/main/java/com/posthog/internal/PostHogQueue.kt#L417-L422
There was a problem hiding this comment.
timeIntervalSince1970-v7 is also fine, not a blocker, just not needed because the approach above would solve the sorting
There was a problem hiding this comment.
Okay implemented by modification date c33a357 even though API is not as elegant as Android
replied |
💡 Motivation and Context
The
@ReadWriteLockproperty wrapper provides a false sense of thread-safety for collection types because the lock is acquired and released between separate operations, allowing the underlying collection to change between a check and a mutation.💚 How did you test it?
Added concurrency tests covering concurrent adds, deletes, and mixed operations.
📝 Checklist