diff --git a/src/commands/install/protocol/resolver.cr b/src/commands/install/protocol/resolver.cr index caf4e72..af891f6 100644 --- a/src/commands/install/protocol/resolver.cr +++ b/src/commands/install/protocol/resolver.cr @@ -67,7 +67,7 @@ abstract struct Zap::Commands::Install::Protocol::Resolver else packages_ref = "#{name}@#{pinned_dependency}" end - state.lockfile.packages_lock.synchronize do + state.lockfile.packages_lock.read do state.lockfile.packages[packages_ref]? end end diff --git a/src/commands/install/resolver.cr b/src/commands/install/resolver.cr index 750505b..dbca39b 100644 --- a/src/commands/install/resolver.cr +++ b/src/commands/install/resolver.cr @@ -153,7 +153,7 @@ module Zap::Commands::Install::Resolver lockfile_cached = uninitialized Bool metadata = keyed_lock(metadata_key) do # If another fiber has already resolved the package, use the cached metadata - lockfile_metadata = state.lockfile.packages_lock.synchronize do + lockfile_metadata = state.lockfile.packages_lock.read do state.lockfile.packages[metadata_key]? end lockfile_cached = !!lockfile_metadata @@ -181,7 +181,7 @@ module Zap::Commands::Install::Resolver # Remove dev dependencies _metadata.dev_dependencies = nil # Store the package data in the lockfile - state.lockfile.packages_lock.synchronize do + state.lockfile.packages_lock.write do state.lockfile.packages[metadata_key] = _metadata end end diff --git a/src/lockfile.cr b/src/lockfile.cr index 9a94e3f..3431f2e 100644 --- a/src/lockfile.cr +++ b/src/lockfile.cr @@ -3,6 +3,7 @@ require "yaml" require "msgpack" require "digest" require "./utils/macros" +require "./utils/concurrent/rwlock" alias DependencyType = ::Zap::Package::DependencyType @@ -47,7 +48,7 @@ class Zap::Lockfile @roots_lock = Mutex.new @[YAML::Field(ignore: true)] @[MessagePack::Field(ignore: true)] - getter packages_lock = Mutex.new + getter packages_lock = Utils::Concurrent::RWLock.new @[YAML::Field(ignore: true)] @[MessagePack::Field(ignore: true)] property read_status : ReadStatus = ReadStatus::NotFound diff --git a/src/utils/concurrent/rwlock.cr b/src/utils/concurrent/rwlock.cr new file mode 100644 index 0000000..b404a8d --- /dev/null +++ b/src/utils/concurrent/rwlock.cr @@ -0,0 +1,50 @@ +class Zap::Utils::Concurrent::RWLock + @writer = Atomic(Int32).new(0) + @readers = Atomic(Int32).new(0) + + def read_lock + loop do + while @writer.get != 0 + Intrinsics.pause + end + + @readers.add(1) + + break if @writer.get == 0 + + @readers.sub(1) + end + end + + def read_unlock + @readers.sub(1) + end + + def read + read_lock + yield + ensure + read_unlock + end + + def write_lock + while @writer.swap(1) != 0 + Intrinsics.pause + end + + while @readers.get != 0 + Intrinsics.pause + end + end + + def write_unlock + @writer.set(0) + end + + def write + write_lock + yield + ensure + write_unlock + end +end diff --git a/src/utils/data_structures/safe_array.cr b/src/utils/data_structures/safe_array.cr index 69f9ba7..5d89964 100644 --- a/src/utils/data_structures/safe_array.cr +++ b/src/utils/data_structures/safe_array.cr @@ -1,20 +1,54 @@ {% if flag?(:preview_mt) %} + require "../concurrent/rwlock" + struct SafeArray(T) property inner : Array(T) - @lock = Mutex.new + @lock = Zap::Utils::Concurrent::RWLock.new def initialize(*args, **kwargs) @inner = Array(T).new(*args, **kwargs) end - def synchronize - @lock.synchronize do - yield @inner + {% begin %} + {% write_methods = [ + :[]=, + :<<, + :clear, + :concat, + :compact!, + :delete, + :delete_at, + :fill, + :insert, + :pop, + :pop?, + :push, + :reject!, + :replace, + :rotate!, + :select!, + :shift, + :shift?, + :sort!, + :sort_by!, + :truncate, + :uniq!, + :unshift, + :unstable_sort!, + :unstable_sort_by!, + ] %} + + {% for write_method in write_methods %} + def {{write_method.id}}(*args, **kwargs) + @lock.write do + @inner.{{write_method.id}}(*args, **kwargs) + end end - end + {% end %} + {% end %} macro method_missing(call) - @lock.synchronize do + @lock.read do @inner.\{{call}} end end diff --git a/src/utils/data_structures/safe_hash.cr b/src/utils/data_structures/safe_hash.cr index 7a8830e..81cd58d 100644 --- a/src/utils/data_structures/safe_hash.cr +++ b/src/utils/data_structures/safe_hash.cr @@ -1,9 +1,10 @@ {% if flag?(:preview_mt) %} require "msgpack" + require "../concurrent/rwlock" struct SafeHash(K, V) getter inner : Hash(K, V) - getter lock = Mutex.new(:reentrant) + getter lock = Zap::Utils::Concurrent::RWLock.new def initialize(*args, **kwargs) @inner = Hash(K, V).new(*args, **kwargs) @@ -13,18 +14,38 @@ @inner = Hash(K, V).new(*args, **kwargs, &block) end - def synchronize - @lock.synchronize do - yield @inner - end - end - def to_msgpack(packer : MessagePack::Packer) @inner.to_msgpack(packer) end + {% begin %} + {% write_methods = [ + :[]=, + :clear, + :compact!, + :delete, + :merge!, + :put, + :put_if_absent, + :reject!, + :select!, + :shift, + :shift?, + :transform_values!, + :update, + ] %} + + {% for write_method in write_methods %} + def {{write_method.id}}(*args, **kwargs) + @lock.write do + @inner.{{write_method.id}}(*args, **kwargs) + end + end + {% end %} + {% end %} + macro method_missing(call) - @lock.synchronize do + @lock.read do @inner.\{{call}} end end diff --git a/src/utils/data_structures/safe_set.cr b/src/utils/data_structures/safe_set.cr index d18f55f..d9b78a9 100644 --- a/src/utils/data_structures/safe_set.cr +++ b/src/utils/data_structures/safe_set.cr @@ -1,14 +1,35 @@ {% if flag?(:preview_mt) %} struct SafeSet(T) property inner : Set(T) - getter lock = Mutex.new(:reentrant) + getter lock = Zap::Utils::Concurrent::RWLock.new def initialize(*args, **kwargs) @inner = Set(T).new(*args, **kwargs) end + {% begin %} + {% write_methods = [ + :<<, + :add, + :add?, + :clear, + :concat, + :delete, + :rehash, + :substract, + ] %} + + {% for write_method in write_methods %} + def {{write_method.id}}(*args, **kwargs) + @lock.write do + @inner.{{write_method.id}}(*args, **kwargs) + end + end + {% end %} + {% end %} + macro method_missing(call) - @lock.synchronize do + @lock.read do @inner.\{{call}} end end