├── bin ├── setup └── console ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── test.yml │ └── push_gem.yml ├── Gemfile ├── Rakefile ├── mutex_m.gemspec ├── BSDL ├── test ├── test_rbs_types.rb └── test_mutex_m.rb ├── README.md ├── sig └── mutex_m.rbs ├── COPYING └── lib └── mutex_m.rb /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | Gemfile.lock 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require_relative "../lib/mutex_m" 5 | 6 | require "irb" 7 | IRB.start(__FILE__) 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | if RUBY_VERSION >= "3.0.0" 6 | gem "rbs", "~> 3.4" 7 | gem "rdoc" 8 | end 9 | 10 | group :development do 11 | gem "bundler" 12 | gem "rake" 13 | gem "test-unit" 14 | end 15 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | task :default => :test 11 | 12 | namespace :rbs do 13 | desc "Update embedded RDoc comments in RBS files" 14 | task :rdoc do 15 | sh "rm -rf tmp/rbs/ri" 16 | sh "mkdir -p tmp/rbs" 17 | sh "rdoc lib --format=ri -o tmp/rbs/ri" 18 | sh "rbs annotate --dir=tmp/rbs/ri --no-system --no-filename sig" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /mutex_m.gemspec: -------------------------------------------------------------------------------- 1 | begin 2 | require_relative "lib/mutex_m" 3 | rescue LoadError 4 | # for Ruby core repository 5 | require_relative "mutex_m" 6 | end 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = "mutex_m" 10 | spec.version = Mutex_m::VERSION 11 | spec.authors = ["Keiju ISHITSUKA"] 12 | spec.email = ["keiju@ruby-lang.org"] 13 | 14 | spec.summary = "Mixin to extend objects to be handled like a Mutex." 15 | spec.description = spec.summary 16 | spec.homepage = "https://github.com/ruby/mutex_m" 17 | spec.metadata["source_code_uri"] = spec.homepage 18 | spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues" 19 | spec.licenses = %w[Ruby BSD-2-Clause] 20 | 21 | spec.files = %w[BSDL COPYING README.md lib/mutex_m.rb sig/mutex_m.rbs] 22 | spec.required_ruby_version = ">= 2.5" 23 | end 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | engine: cruby 10 | min_version: 2.5 11 | 12 | test: 13 | needs: ruby-versions 14 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 15 | strategy: 16 | matrix: 17 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 18 | os: [ ubuntu-latest, macos-latest ] 19 | exclude: 20 | - {os: macos-latest, ruby: 2.5} 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v6 24 | - name: Set up Ruby 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | - name: Install dependencies 29 | run: bundle install 30 | - name: Run test 31 | run: rake test 32 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/mutex_m' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/mutex_m 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: ruby 37 | 38 | - name: Publish to RubyGems 39 | uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2 40 | 41 | - name: Create GitHub release 42 | run: | 43 | tag_name="$(git describe --tags --abbrev=0)" 44 | gh release create "${tag_name}" --verify-tag --generate-notes 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /test/test_rbs_types.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'mutex_m' 3 | begin 4 | require 'rbs/unit_test' 5 | rescue LoadError 6 | return 7 | end 8 | 9 | module RBSTypeTest 10 | class Mutex_mInstanceTest < Test::Unit::TestCase 11 | include RBS::UnitTest::TypeAssertions 12 | 13 | library 'mutex_m' 14 | testing "::Mutex_m" 15 | 16 | def mu 17 | Object.new.tap do |o| 18 | o.extend Mutex_m 19 | end 20 | end 21 | 22 | def test_mu_lock 23 | assert_send_type "() -> Thread::Mutex", 24 | mu, :mu_lock 25 | end 26 | 27 | def test_mu_locked? 28 | mu = mu() 29 | assert_send_type "() -> false", 30 | mu, :mu_locked? 31 | mu.lock 32 | assert_send_type "() -> true", 33 | mu, :mu_locked? 34 | end 35 | 36 | def test_mu_synchronize 37 | assert_send_type "() { () -> String } -> String", 38 | mu, :mu_synchronize do 'foo' end 39 | end 40 | 41 | def test_mu_try_lock 42 | assert_send_type "() -> bool", 43 | mu, :mu_try_lock 44 | end 45 | 46 | def test_mu_unlock 47 | mu = mu() 48 | mu.lock 49 | assert_send_type "() -> Thread::Mutex", 50 | mu, :mu_unlock 51 | end 52 | 53 | def test_sleep 54 | mu = mu() 55 | mu.lock 56 | assert_send_type "(Integer) -> Integer?", 57 | mu, :sleep, 0 58 | assert_send_type "(Float) -> Integer?", 59 | mu, :sleep, 0.1 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mutex_m 2 | 3 | When 'mutex_m' is required, any object that extends or includes Mutex_m will be treated like a Mutex. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'mutex_m' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install mutex_m 20 | 21 | ## Usage 22 | 23 | Start by requiring the standard library Mutex_m: 24 | 25 | ```ruby 26 | require "mutex_m" 27 | ``` 28 | 29 | From here you can extend an object with Mutex instance methods: 30 | 31 | ```ruby 32 | obj = Object.new 33 | obj.extend Mutex_m 34 | ``` 35 | 36 | Or mixin Mutex_m into your module to your class inherit Mutex instance methods. 37 | You should probably use `prepend` to mixin (if you use `include`, you need to 38 | make sure that you call `super` inside `initialize`). 39 | 40 | ```ruby 41 | class Foo 42 | prepend Mutex_m 43 | # ... 44 | end 45 | 46 | obj = Foo.new 47 | # this obj can be handled like Mutex 48 | ``` 49 | 50 | ## Development 51 | 52 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 53 | 54 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 55 | 56 | ## Contributing 57 | 58 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/mutex_m. 59 | 60 | ## License 61 | 62 | The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). 63 | -------------------------------------------------------------------------------- /sig/mutex_m.rbs: -------------------------------------------------------------------------------- 1 | # # mutex_m.rb 2 | # 3 | # When 'mutex_m' is required, any object that extends or includes Mutex_m will 4 | # be treated like a Mutex. 5 | # 6 | # Start by requiring the standard library Mutex_m: 7 | # 8 | # require "mutex_m" 9 | # 10 | # From here you can extend an object with Mutex instance methods: 11 | # 12 | # obj = Object.new 13 | # obj.extend Mutex_m 14 | # 15 | # Or mixin Mutex_m into your module to your class inherit Mutex instance methods 16 | # --- remember to call super() in your class initialize method. 17 | # 18 | # class Foo 19 | # include Mutex_m 20 | # def initialize 21 | # # ... 22 | # super() 23 | # end 24 | # # ... 25 | # end 26 | # obj = Foo.new 27 | # # this obj can be handled like Mutex 28 | # 29 | module Mutex_m 30 | def self.append_features: (Module cl) -> untyped 31 | 32 | def self.define_aliases: (Module cl) -> untyped 33 | 34 | def self.extend_object: (Object obj) -> untyped 35 | 36 | public 37 | 38 | def mu_extended: () -> untyped 39 | 40 | # 43 | # See Thread::Mutex#lock 44 | # 45 | def mu_lock: () -> Thread::Mutex 46 | 47 | # 50 | # See Thread::Mutex#locked? 51 | # 52 | def mu_locked?: () -> bool 53 | 54 | # 57 | # See Thread::Mutex#synchronize 58 | # 59 | def mu_synchronize: [T] () { () -> T } -> T 60 | 61 | # 64 | # See Thread::Mutex#try_lock 65 | # 66 | def mu_try_lock: () -> bool 67 | 68 | # 71 | # See Thread::Mutex#unlock 72 | # 73 | def mu_unlock: () -> Thread::Mutex 74 | 75 | # 78 | # See Thread::Mutex#sleep 79 | # 80 | def sleep: (?Numeric timeout) -> Integer? 81 | 82 | alias locked? mu_locked? 83 | 84 | alias lock mu_lock 85 | 86 | alias unlock mu_unlock 87 | 88 | alias try_lock mu_try_lock 89 | 90 | alias synchronize mu_synchronize 91 | 92 | private 93 | 94 | def initialize: (*untyped args) -> untyped 95 | 96 | def mu_initialize: () -> untyped 97 | end 98 | 99 | Mutex_m::VERSION: String 100 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /test/test_mutex_m.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'mutex_m' 4 | 5 | class TestMutexM < Test::Unit::TestCase 6 | def test_cv_wait 7 | o = Object.new 8 | o.instance_variable_set(:@foo, nil) 9 | o.extend(Mutex_m) 10 | c = Thread::ConditionVariable.new 11 | t = Thread.start { 12 | o.synchronize do 13 | until foo = o.instance_variable_get(:@foo) 14 | c.wait(o) 15 | end 16 | foo 17 | end 18 | } 19 | sleep(0.0001) 20 | o.synchronize do 21 | o.instance_variable_set(:@foo, "abc") 22 | end 23 | c.signal 24 | assert_equal "abc", t.value 25 | end 26 | 27 | class KeywordInitializeParent 28 | def initialize(x:) 29 | end 30 | end 31 | 32 | class KeywordInitializeChild < KeywordInitializeParent 33 | include Mutex_m 34 | def initialize 35 | super(x: 1) 36 | end 37 | end 38 | 39 | def test_initialize_with_keyword_arg 40 | assert KeywordInitializeChild.new 41 | end 42 | 43 | class NoArgInitializeParent 44 | def initialize 45 | end 46 | end 47 | 48 | class NoArgInitializeChild < NoArgInitializeParent 49 | include Mutex_m 50 | def initialize 51 | super() 52 | end 53 | end 54 | 55 | def test_initialize_no_args 56 | assert NoArgInitializeChild.new 57 | end 58 | 59 | class PositionalArgInitializeParent 60 | attr_reader :x 61 | 62 | def initialize(x) 63 | @x = x 64 | end 65 | end 66 | 67 | def test_include 68 | c = PositionalArgInitializeParent.dup 69 | c.class_eval do 70 | alias initialize initialize 71 | def initialize(x) 72 | @x = x 73 | super() 74 | end 75 | include Mutex_m 76 | end 77 | o = c.new(1) 78 | assert_equal(1, o.synchronize{o.x}) 79 | end 80 | 81 | def test_prepend 82 | c = PositionalArgInitializeParent.dup 83 | c.prepend Mutex_m 84 | o = c.new(1) 85 | assert_equal(1, o.synchronize{o.x}) 86 | end 87 | 88 | def test_include_sub 89 | c = Class.new(PositionalArgInitializeParent) 90 | c.include Mutex_m 91 | o = c.new(1) 92 | assert_equal(1, o.synchronize{o.x}) 93 | end 94 | 95 | def test_prepend_sub 96 | c = Class.new(PositionalArgInitializeParent) 97 | c.prepend Mutex_m 98 | o = c.new(1) 99 | assert_equal(1, o.synchronize{o.x}) 100 | end 101 | 102 | def test_alias_extended_object 103 | object = Object.new 104 | object.extend(Mutex_m) 105 | 106 | assert object.respond_to?(:locked?) 107 | assert object.respond_to?(:lock) 108 | assert object.respond_to?(:unlock) 109 | assert object.respond_to?(:try_lock) 110 | assert object.respond_to?(:synchronize) 111 | end 112 | 113 | def test_alias_included_class 114 | object = NoArgInitializeChild.new 115 | 116 | assert object.respond_to?(:locked?) 117 | assert object.respond_to?(:lock) 118 | assert object.respond_to?(:unlock) 119 | assert object.respond_to?(:try_lock) 120 | assert object.respond_to?(:synchronize) 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/mutex_m.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # 3 | # mutex_m.rb - 4 | # $Release Version: 3.0$ 5 | # $Revision: 1.7 $ 6 | # Original from mutex.rb 7 | # by Keiju ISHITSUKA(keiju@ishitsuka.com) 8 | # modified by matz 9 | # patched by akira yamada 10 | # 11 | # -- 12 | 13 | # = mutex_m.rb 14 | # 15 | # When 'mutex_m' is required, any object that extends or includes Mutex_m will 16 | # be treated like a Mutex. 17 | # 18 | # Start by requiring the standard library Mutex_m: 19 | # 20 | # require "mutex_m" 21 | # 22 | # From here you can extend an object with Mutex instance methods: 23 | # 24 | # obj = Object.new 25 | # obj.extend Mutex_m 26 | # 27 | # Or mixin Mutex_m into your module to your class inherit Mutex instance 28 | # methods --- remember to call super() in your class initialize method. 29 | # 30 | # class Foo 31 | # include Mutex_m 32 | # def initialize 33 | # # ... 34 | # super() 35 | # end 36 | # # ... 37 | # end 38 | # obj = Foo.new 39 | # # this obj can be handled like Mutex 40 | # 41 | module Mutex_m 42 | 43 | VERSION = "0.3.0" 44 | Ractor.make_shareable(VERSION) if defined?(Ractor) 45 | 46 | def Mutex_m.define_aliases(cl) # :nodoc: 47 | cl.alias_method(:locked?, :mu_locked?) 48 | cl.alias_method(:lock, :mu_lock) 49 | cl.alias_method(:unlock, :mu_unlock) 50 | cl.alias_method(:try_lock, :mu_try_lock) 51 | cl.alias_method(:synchronize, :mu_synchronize) 52 | end 53 | 54 | def Mutex_m.prepend_features(cl) # :nodoc: 55 | super 56 | define_aliases(cl) unless cl.instance_of?(Module) 57 | end 58 | 59 | def Mutex_m.append_features(cl) # :nodoc: 60 | super 61 | define_aliases(cl) unless cl.instance_of?(Module) 62 | end 63 | 64 | def Mutex_m.extend_object(obj) # :nodoc: 65 | super 66 | obj.mu_extended 67 | end 68 | 69 | def mu_extended # :nodoc: 70 | unless (defined? locked? and 71 | defined? lock and 72 | defined? unlock and 73 | defined? try_lock and 74 | defined? synchronize) 75 | Mutex_m.define_aliases(singleton_class) 76 | end 77 | mu_initialize 78 | end 79 | 80 | # See Thread::Mutex#synchronize 81 | def mu_synchronize(&block) 82 | @_mutex.synchronize(&block) 83 | end 84 | 85 | # See Thread::Mutex#locked? 86 | def mu_locked? 87 | @_mutex.locked? 88 | end 89 | 90 | # See Thread::Mutex#try_lock 91 | def mu_try_lock 92 | @_mutex.try_lock 93 | end 94 | 95 | # See Thread::Mutex#lock 96 | def mu_lock 97 | @_mutex.lock 98 | end 99 | 100 | # See Thread::Mutex#unlock 101 | def mu_unlock 102 | @_mutex.unlock 103 | end 104 | 105 | # See Thread::Mutex#sleep 106 | def sleep(timeout = nil) 107 | @_mutex.sleep(timeout) 108 | end 109 | 110 | private 111 | 112 | def mu_initialize # :nodoc: 113 | @_mutex = Thread::Mutex.new 114 | end 115 | 116 | def initialize(*args) # :nodoc: 117 | mu_initialize 118 | super 119 | end 120 | ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true) 121 | end 122 | --------------------------------------------------------------------------------