Skip to content

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

Merged
merged 12 commits into from
Oct 21, 2020

Conversation

tgymnich
Copy link
Contributor

@tgymnich tgymnich commented Oct 6, 2020

This PR contains the following improvements to locks needed for SR-12851.

1. Added ReadWriteLock. (Using GCD instead for windows compatiblity)
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. --> Making FileLock thread safe
3. Enabled FileLock to obtain an exclusive or a shared lock.

This is PR #130 reopened on the main branch.

@MaxDesiatov
Copy link
Contributor

@swift-ci please test

@MaxDesiatov
Copy link
Contributor

@swift-ci please test

if let queue = lockFiles[path] {
fileQueue = queue
} else {
fileQueue = DispatchQueue(label: "foo", attributes: .concurrent)
Copy link
Contributor

Choose a reason for hiding this comment

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

foo?

Copy link
Contributor Author

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

fileQueue = DispatchQueue(label: "foo", attributes: .concurrent)
lockFiles[path] = fileQueue
}
lockFilesLock.unlock()
Copy link
Contributor

Choose a reason for hiding this comment

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

use lock.withLock?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor

@abertelrud abertelrud Oct 20, 2020

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?

Copy link
Contributor

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

@@ -11,6 +11,11 @@
import Foundation
import TSCLibc

public enum LockType {
Copy link
Contributor

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?

Copy link
Contributor Author

@tgymnich tgymnich Oct 20, 2020

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.

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

@abertelrud abertelrud left a 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> {
Copy link
Contributor

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.

@@ -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>()
Copy link
Contributor

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.

Copy link
Contributor

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

}

/// `ThreadLocal` properties are thread-specific. Every thread has its own instance of the wrapped property.
@propertyWrapper final class ThreadLocal<Value: Defaultable> {
Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor Author

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.

}

/// Automatically closes the wrapped file descriptor on deinit.
@propertyWrapper final class AutoClosing: NSObject, Defaultable {
Copy link
Contributor

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/).

Copy link
Contributor

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

Copy link
Contributor

@tomerd tomerd left a 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.

@tomerd
Copy link
Contributor

tomerd commented Oct 20, 2020

also fwiw would love this see this merged as its needed for other use cases as well!

@tgymnich
Copy link
Contributor Author

tgymnich commented Oct 20, 2020

@abertelrud @tomerd thank you for your feedback! I will start a discussion on the forums about adding @AutoClosing to SwiftSystem.

Update:
@AutoClosing Forum Post

@tgymnich tgymnich requested a review from abertelrud October 20, 2020 14:10
@abertelrud
Copy link
Contributor

abertelrud commented Oct 20, 2020

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!

@@ -48,7 +48,9 @@ add_library(TSCBasic
TerminalController.swift
Thread.swift
Tuple.swift
misc.swift)
misc.swift
WeakDictionary.swift)
Copy link
Contributor

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?

Copy link
Contributor

@abertelrud abertelrud Oct 20, 2020

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).

@tomerd
Copy link
Contributor

tomerd commented Oct 20, 2020

@swift-ci please test

Copy link
Contributor

@tomerd tomerd left a 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

@abertelrud
Copy link
Contributor

I think the reference to WeakDictionary.swift in the CMake file might cause problems for bootstrapping. Apart from that I just had a suggestion for the withLock closure, but this looks great, with or without adopting that suggestion. Thanks!

@@ -48,7 +48,9 @@ add_library(TSCBasic
TerminalController.swift
Thread.swift
Tuple.swift
misc.swift)
misc.swift
WeakDictionary.swift)
Copy link
Contributor

@abertelrud abertelrud Oct 20, 2020

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).

@tgymnich tgymnich requested a review from abertelrud October 20, 2020 21:23
@tomerd
Copy link
Contributor

tomerd commented Oct 20, 2020

@swift-ci please test

@tomerd tomerd assigned abertelrud and unassigned neonichu Oct 20, 2020
@tomerd
Copy link
Contributor

tomerd commented Oct 20, 2020

thanks @tgymnich 👍

Copy link
Contributor

@abertelrud abertelrud left a 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!

@abertelrud
Copy link
Contributor

@tgymnich All the tests pass, so all good if I merge this at this point?

@tgymnich
Copy link
Contributor Author

all good 🚀

@neonichu neonichu merged commit a77e5a0 into swiftlang:main Oct 21, 2020
@neonichu
Copy link
Contributor

Thanks a lot, @tgymnich!

@compnerd
Copy link
Member

This seems to be causing some issues on Windows :-(

compnerd added a commit to compnerd/swift-tools-support-core that referenced this pull request Oct 21, 2020
Import WinSDK to get access to `SwitchToThread`.
@tgymnich
Copy link
Contributor Author

sorry about that

neonichu added a commit that referenced this pull request Oct 22, 2020
TSCBasic: repair the build on Windows after #139
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.

6 participants