-
Notifications
You must be signed in to change notification settings - Fork 129
Lock Improvements for [SR-12851] #139
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
Conversation
@swift-ci please test |
bc582aa
to
5361cfb
Compare
@swift-ci please test |
Sources/TSCBasic/FileSystem.swift
Outdated
if let queue = lockFiles[path] { | ||
fileQueue = queue | ||
} else { | ||
fileQueue = DispatchQueue(label: "foo", attributes: .concurrent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
foo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to remove the placeholder. This should be org.swift.swiftpm.in-memory-file-system.file-queue
Sources/TSCBasic/FileSystem.swift
Outdated
fileQueue = DispatchQueue(label: "foo", attributes: .concurrent) | ||
lockFiles[path] = fileQueue | ||
} | ||
lockFilesLock.unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use lock.withLock?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I would use lock.withLock
I cannot leave var fileQueue: DispatchQueue
unassigned. Making it optional would mean that I somehow have to handle the case where fileQueue
is nil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it could be defined as var fileQueue: DispatchQueue!
, or do we not use this pattern in this repo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally try to avoid force unwrap, but there are exceptions if it makes the rest of the code safer and if there's really no way for the force unwrap to fail.
But in this case I think withLock
on a regular Lock
is also declared as public func withLock<T>(_ body: () throws -> T) rethrows -> T
, so could you return the fileQueue
from the withLock
closure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But in this case I think withLock on a regular Lock is also declared as public func withLock(_ body: () throws -> T) rethrows -> T, so could you return the fileQueue from the withLock closure?
that's a great idea
Sources/TSCBasic/Lock.swift
Outdated
@@ -11,6 +11,11 @@ | |||
import Foundation | |||
import TSCLibc | |||
|
|||
public enum LockType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this renamed to FileLockType
or nested under FileLock
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to keep the name unchanged since FileLock
might not be the only shared lock in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point, but personally prefer to follow the rule of three with generalizations. since this is not a stable API I am not too worried about API surface area, but as a rule of thumb introducing top level types is something to do with care
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since API stability is of no concern let's put it inside of FileLock
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for your work here!
I have a few comments about some of the more generic additions to ToolSupportCore, some of which I hope won't be needed soon because of SwiftSystem (https://swift.org/blog/swift-system/) and others that would be better as separate PRs, if they should be in ToolsSupportCore.
Would it be possible to focus this PR on just the file locking, even if it means keeping a bit more code in that class, and discussing how to factor this out as part of other PRs? Thanks!
*/ | ||
|
||
/// A dictionary that only keeps weak references to its values. | ||
struct WeakDictionary<Key: Hashable, Value: AnyObject> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this isn't a generally usable dictionary (it doesn't have the regular Dictionary operations), would it be better for this use case to have the one usage case of WeakDictionary in this PR have a regular Dictionary with a weak reference as its value (using a private struct in the class that needs it)? My concern would be that this looks like a general-type of purpose Dictionary but can't be used as such, since it doesn't conform to the other Dictionary API.
Sources/TSCBasic/FileSystem.swift
Outdated
@@ -502,6 +515,10 @@ public class InMemoryFileSystem: FileSystem { | |||
|
|||
/// The root filesytem. | |||
private var root: Node | |||
/// A map that keeps weak references to all locked files. | |||
private var lockFiles = WeakDictionary<AbsolutePath, DispatchQueue>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this seems to be the only use of WeakDictionary, I think it might be simpler in this case to use a regular Dictionary whose value is a weak reference (which might require a private struct in this case). This is in reference to the comment for the WeakDictionary.swift file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for proposing such generalizations as separate followup PR so can be discussed individually
Sources/TSCBasic/Thread.swift
Outdated
} | ||
|
||
/// `ThreadLocal` properties are thread-specific. Every thread has its own instance of the wrapped property. | ||
@propertyWrapper final class ThreadLocal<Value: Defaultable> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ThreadLocal as a property wrapper seems very useful, but feels too general for this PR. It feels more like a language-level feature, and we can discuss whether that should be in ToolsSupportCore.
For this case, with the file locking, would it be possible to use Foundation.Thread's threadDictionary
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for proposing such generalizations as separate followup PR so can be discussed individually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would be easier to leave FileLock
thread-unsafe for the moment and to fix this issue once @ThreadLocal
and @AutoClosing
are available in SwiftSystem or ToolSupportCore.
Sources/TSCBasic/Thread.swift
Outdated
} | ||
|
||
/// Automatically closes the wrapped file descriptor on deinit. | ||
@propertyWrapper final class AutoClosing: NSObject, Defaultable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As with the other property wrapper, I think this could have a lot of utility but think it would be better to discuss as a follow-on PR to simplify the code. I'm also hopeful that things like this will be able to come from SwiftSystem so that ToolSupportCore doesn't need to implement it (https://swift.org/blog/swift-system/).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for proposing such generalizations as separate followup PR so can be discussed individually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is great @tgymnich. left some suggestions inline.
also fwiw would love this see this merged as its needed for other use cases as well! |
@abertelrud @tomerd thank you for your feedback! I will start a discussion on the forums about adding Update: |
Thank you very much for making the changes and for starting that post! To be clear, I think such abstractions are a great idea, but would love to see them available more generally for all Swift packages, and after broader discussion. ToolsSupportCore originally came about because there was a lot of general-purpose code written for SwiftPM that didn't really belong in the package manager itself. But I'm really excited about SwiftSystem and some of the other packages (e.g. SwiftArgumentParser). Hopefully we'll be able to replace more and more of ToolsSupportCore with functionality from other more "polished" packages over time. And to echo what Tom said, I, too, am really excited about the repository caching and am really eager to see it become part of SwiftPM! I'll take a look at the revised PR in the next few hours. Thanks! |
Sources/TSCBasic/CMakeLists.txt
Outdated
@@ -48,7 +48,9 @@ add_library(TSCBasic | |||
TerminalController.swift | |||
Thread.swift | |||
Tuple.swift | |||
misc.swift) | |||
misc.swift | |||
WeakDictionary.swift) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still in use, or leftover?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WeakDictionary.swift
was removed, so this will probably cause issues for bootstrapping using CMake. (I thought the full test did a bootstrap, actually, but the test seems to have passed).
@swift-ci please test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks great, one comment
I think the reference to |
Sources/TSCBasic/CMakeLists.txt
Outdated
@@ -48,7 +48,9 @@ add_library(TSCBasic | |||
TerminalController.swift | |||
Thread.swift | |||
Tuple.swift | |||
misc.swift) | |||
misc.swift | |||
WeakDictionary.swift) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WeakDictionary.swift
was removed, so this will probably cause issues for bootstrapping using CMake. (I thought the full test did a bootstrap, actually, but the test seems to have passed).
@swift-ci please test |
thanks @tgymnich 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @tgymnich, this looks great now! I really appreciate your patience and persistence with this!
@tgymnich All the tests pass, so all good if I merge this at this point? |
all good 🚀 |
Thanks a lot, @tgymnich! |
This seems to be causing some issues on Windows :-( |
Import WinSDK to get access to `SwitchToThread`.
sorry about that |
TSCBasic: repair the build on Windows after #139
This PR contains the following improvements to locks needed for SR-12851.
1. Added(Using GCD instead for windows compatiblity)ReadWriteLock
.2. Added property wrapper @ThreadLocal to address an issue where
FileLock
could be used in way that would unlock/lock the lock from another thread without the user realizing. --> MakingFileLock
thread safe3. Enabled
FileLock
to obtain an exclusive or a shared lock.This is PR #130 reopened on the main branch.