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

Improve NSFileManager thread safety #306

Merged
merged 1 commit into from
Jul 26, 2023
Merged

Conversation

triplef
Copy link
Member

@triplef triplef commented Jul 19, 2023

This is a shallow fix for possible crashes when concurrently calling various APIs which internally use +[NSFileManager defaultManager], such as -[NSData writeToFile:options:error:].

Guards access to the _lastError instance variable with a lock, so that concurrent invocations of NSFileManager methods modifying the variable will not e.g. cause over-release of objects.

This is mainly meant to fix crashes when no error occurs. Note however that the actual error handling is not made thread safe with this fix, i.e. if an error occurs while another method is concurrently using the file manager APIs there can still be crashes and incorrect control flow. I see two ways to fix this properly:

  1. Replace the _lastError instance variable with local error variables that are passed through the method calls.
  2. Replace the use of defaultManager with individual NSFileManager objects in the various APIs using NSFileManager.

Both of these seem somewhat involved, so I wanted to get this shallow fix in first. If preferred I’m happy to keep the patch locally for now.

@triplef triplef requested a review from rfm as a code owner July 19, 2023 10:47
@rfm
Copy link
Contributor

rfm commented Jul 24, 2023

I have been thinking about this. Both of the proposed long term solutions have problems:
We can't generally pass information via the method calls, because we want to be compatible with Apple, so the method calls are restricted to the parameters as defined by them.
In light of the fact that the current apple documentation days that methods of the shared manager are thread-safe, we probably shouldn't be creating lots of individual instances.

So, what do you think of changing the code to keep error information in a thread-local variable rather than as an instance variable? I think that would allow us to have instances (including the shared instance) be thread safe like the current apple implementation, avoiding either changing the methods to pass extra information or having lots of temporary instances created.

@triplef
Copy link
Member Author

triplef commented Jul 24, 2023

Ah yes, great idea.

Should I just replace the _lastError ivar with using GS_THREAD_KEY_*()? I guess a local getter/setter (lastError/setLastError:) would make sense to wrap the TLS access?

@triplef
Copy link
Member Author

triplef commented Jul 24, 2023

I guess using GSCurrentThreadDictionary() is more in line with what other codes does.

@rfm
Copy link
Contributor

rfm commented Jul 24, 2023

GSCurrentThreadDictionary() is much less efficient, but more reliable for cleanup because the dictionary and its contents will reliably be destroyed at thread end.
Since the context here is file operations, which involve I/O and can be expected to be relatively slow, I think the slower but safer code would probably be fine.

@triplef triplef force-pushed the fix-nsfilemanager-thread-safety branch from ccef81f to 9cf2647 Compare July 25, 2023 08:32
@triplef triplef force-pushed the fix-nsfilemanager-thread-safety branch from 9cf2647 to f62ecfa Compare July 25, 2023 08:37
@triplef
Copy link
Member Author

triplef commented Jul 25, 2023

Thanks. I ended up using the TLS macros because it didn’t seem too involved to make that work. The only slightly tricky part is support for ARC, as the current RETAIN/RELEASE() etc. macros don’t work for this case, but I think I got it working too.

If you see any issues with this approach I can also switch out the getter/setter to use GSCurrentThreadDictionary().

@rfm
Copy link
Contributor

rfm commented Jul 25, 2023

Actually, after sleeping on it, I think you made the right choice. The automatic cleanup at the end of the thread does not ensure cleanup at the end of the lifetime of an NSFileManager instance, and we'd probably want to do that anyway. So the simpler and more efficient macros seem good.

@triplef
Copy link
Member Author

triplef commented Jul 26, 2023

Great. Is this good to merge then or did you still want to review?

Copy link
Contributor

@rfm rfm left a comment

Choose a reason for hiding this comment

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

It looks good to me. Thanks.

@triplef triplef merged commit f0e33a4 into master Jul 26, 2023
9 checks passed
@triplef triplef deleted the fix-nsfilemanager-thread-safety branch July 26, 2023 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants