-
Notifications
You must be signed in to change notification settings - Fork 27
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
Add optional support for write locks #16
Add optional support for write locks #16
Conversation
Also, with this change, I took the liberty of updating dependencies, mostly to get a patch for (We should evaluate whether it makes sense to continue shipping a |
src/synchronizer.rs
Outdated
pub fn new(path_prefix: &'a OsStr) -> Self { | ||
Self { | ||
path_prefix, | ||
write_lock_mode: WriteLockMode::Disabled, |
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.
Thinking out loud - what if default write mode was SingleWriter
- (aka why would a caller build a Synchronizer
if the default initialization results in a no-op)?
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.
Can you clarify what you mean? Are you suggesting that WriteLockMode::SingleWriter
should be the default at all times (i.e., that we forego the write-lock
feature flag)? Or that it should become the default automatically when the write-lock
feature is enabled?
There are a few reasons this functionality is not the default and is gated behind a feature flag:
- Performance. We want to maintain zero-cost abstractions, or in Stroustrup's words "What you don't use, you don't pay for." @bocharov optimized writer performance in questions regarding low req/s write and how to sync between them as ipc use #11; by placing this code behind a feature flag we avoid performance regression for applications that do not require write locking.
- Compatibility.
flock
is only available on Unix systems, so thelibc
dependency and all calls to it must be gated behind a feature flag to retain the Windows support added in Windows support #3. (In theory, we may be able to add some Windows-compatible locking mechanism in the future; but I'm unlikely to spend any time on this.)
Perhaps we could experiment further with the configuration API. E.g., define a WriteLockStrategy
trait to support this. That would become an additional generic parameter on Synchronizer
, allowing us to skip the builder pattern. Still, I wouldn't make SingleWriter
a default; IMO, toggling the feature flag shouldn't change the configuration of any Synchronizer
without explicitly opting in.
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'm not talking about the feature flag. "that it should become the default automatically when the write-lock feature is enabled?" - was my question.
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.
That carries the risk of changing the behavior of existing code when the flag is enabled/disabled. Typically, feature flags enable/expose new APIs rather than controlling behavior directly. This is best as it should be possible to mix locking and non-locking Synchronizers
even within a single project.
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 I don't really see the distinction between enabling (and ostensibly using) new APIs rather than controlling behaviour. Point being is - what is the use for a Synchronizer
if not locking (ignoring feature flags for the sake of argument)?
0ffc5a1
to
ae17e94
Compare
src/synchronizer.rs
Outdated
pub struct Synchronizer< | ||
H: Hasher + Default = WyHash, | ||
const N: usize = 1024, | ||
const SD: u64 = 1_000_000_000, | ||
WL = Disabled, |
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.
To me it makes more sense to move WL
right after H
, so that it's easier to use synchronizer with N
and SD
defaults.
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.
Updated. Note, this is a breaking change to any code specifying the N
or SD
parameters, so we'll need a major version bump when releasing.
22a7be3
to
2338505
Compare
Updated to address feedback. Also, edited the PR description with new benchmarks and an updated description of the design. Also, sealed the |
89b55d8
to
e1bbaab
Compare
These write locks prevent accidental misuse of `Synchronizer`s by establishing an advisory lock on the state file using the `flock` system call. Correctly configured writers will check this lock to prevent duplicate writers. By default, write locks are disabled and carry no additional cost through use of generics. Unused functionality is optimized out by the compiler during monomorphization. Write locking is only supported on Unix-like platforms due to use of the `flock` API. Other locking mechanisms and lock modes could be supported in the future.
e1bbaab
to
7c4dff6
Compare
Rebased to simplify history. Bumped version to |
Write locks prevent accidental misuse of
Synchronizer
s by establishing an advisory lock on the state file using theflock
system call. Correctly configured writers will check this lock to prevent duplicate writers.Write locking functionality is enabled by choosing a
WriteLockStrategy
and configuring theWL
generic parameter onSynchronizer
.Design
This code is designed to be zero-cost when write locking is disabled. By using a generic parameter, the compiler optimizes away the
LockDisabled
implementation during monomorphization.Currently, we only support
LockDisabled
andSingleWriter
strategies, whereSingleWriter
makes a non-blocking call toflock
to establish a lock. Future work might add support for multiple writers (i.e., where the lock is released after every write so other threads/processes can write), in addition to blocking/non-blocking variations of single- and multiple-writer modes.Internally, the
state
method has been divided intostate_read
andstate_write
to avoid an additional branch condition for reads. This design allows read throughout to remain unchanged.Benchmarks