lease_machines 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ use std::{cell::RefCell, time::Duration};
2
+
3
+ use lease_machines::{
4
+ Checkpoint, Decision, Fence, FencingToken, Lease, LeaseAuthority, OwnerId, Rejection,
5
+ };
6
+ use magnus::typed_data::Obj;
7
+ use magnus::{Error, Integer, RArray, Ruby, TryConvert, Value, function, method, prelude::*};
8
+
9
+ #[derive(Clone, Copy)]
10
+ struct Count(u64);
11
+
12
+ impl TryConvert for Count {
13
+ fn try_convert(value: Value) -> Result<Self, Error> {
14
+ if Integer::from_value(value).is_none() {
15
+ return Err(Error::new(
16
+ Ruby::get_with(value).exception_type_error(),
17
+ "expected an Integer",
18
+ ));
19
+ }
20
+ u64::try_convert(value).map(Self)
21
+ }
22
+ }
23
+
24
+ fn duration(ruby: &Ruby, seconds: f64) -> Result<Duration, Error> {
25
+ Duration::try_from_secs_f64(seconds).map_err(|_| {
26
+ Error::new(
27
+ ruby.exception_arg_error(),
28
+ "time must be finite, nonnegative, and representable",
29
+ )
30
+ })
31
+ }
32
+
33
+ fn token(ruby: &Ruby, value: Count) -> Result<FencingToken, Error> {
34
+ FencingToken::new(value.0)
35
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "token must be positive"))
36
+ }
37
+
38
+ #[magnus::wrap(class = "LeaseMachines::Lease", free_immediately, size)]
39
+ struct Grant(Lease);
40
+
41
+ impl Grant {
42
+ const fn owner(&self) -> u64 {
43
+ self.0.owner().value()
44
+ }
45
+ const fn token(&self) -> u64 {
46
+ self.0.token().value()
47
+ }
48
+ const fn expires_at(&self) -> f64 {
49
+ self.0.expires_at().as_secs_f64()
50
+ }
51
+ }
52
+
53
+ #[magnus::wrap(class = "LeaseMachines::Checkpoint", free_immediately, size)]
54
+ struct Saved(Checkpoint);
55
+
56
+ impl Saved {
57
+ fn last_issued(&self) -> Option<u64> {
58
+ self.0.last_issued().map(FencingToken::value)
59
+ }
60
+ const fn observed(&self) -> f64 {
61
+ self.0.observed().as_secs_f64()
62
+ }
63
+ }
64
+
65
+ fn decision(ruby: &Ruby, value: Decision) -> Result<RArray, Error> {
66
+ let result = ruby.ary_new();
67
+ match value {
68
+ Decision::Granted(lease) | Decision::Renewed(lease) => {
69
+ let name = if matches!(value, Decision::Granted(_)) {
70
+ "granted"
71
+ } else {
72
+ "renewed"
73
+ };
74
+ result.push(ruby.to_symbol(name))?;
75
+ result.push(Grant(lease))?;
76
+ result.push(())?;
77
+ }
78
+ Decision::Released => {
79
+ result.push(ruby.to_symbol("released"))?;
80
+ result.push(())?;
81
+ result.push(())?;
82
+ }
83
+ Decision::Rejected(reason) => {
84
+ let name = match reason {
85
+ Rejection::Occupied => "occupied",
86
+ Rejection::NoLease => "no_lease",
87
+ Rejection::WrongOwner => "wrong_owner",
88
+ Rejection::StaleToken => "stale_token",
89
+ Rejection::Expired => "expired",
90
+ Rejection::ClockRegressed => "clock_regressed",
91
+ Rejection::InvalidTtl => "invalid_ttl",
92
+ Rejection::TokenExhausted => "token_exhausted",
93
+ _ => {
94
+ return Err(Error::new(
95
+ ruby.exception_runtime_error(),
96
+ "unsupported rejection",
97
+ ));
98
+ }
99
+ };
100
+ result.push(ruby.to_symbol("rejected"))?;
101
+ result.push(())?;
102
+ result.push(ruby.to_symbol(name))?;
103
+ }
104
+ _ => {
105
+ return Err(Error::new(
106
+ ruby.exception_runtime_error(),
107
+ "unsupported decision",
108
+ ));
109
+ }
110
+ }
111
+ Ok(result)
112
+ }
113
+
114
+ #[magnus::wrap(class = "LeaseMachines::Authority", free_immediately, size)]
115
+ struct Authority(RefCell<LeaseAuthority>);
116
+
117
+ impl Authority {
118
+ const fn new() -> Self {
119
+ Self(RefCell::new(LeaseAuthority::new()))
120
+ }
121
+ const fn restore(saved: &Saved) -> Self {
122
+ Self(RefCell::new(LeaseAuthority::restore(saved.0)))
123
+ }
124
+ fn checkpoint(&self) -> Saved {
125
+ Saved(self.0.borrow().checkpoint())
126
+ }
127
+ fn acquire(
128
+ ruby: &Ruby,
129
+ this: Obj<Self>,
130
+ owner: Count,
131
+ now: f64,
132
+ ttl: f64,
133
+ ) -> Result<RArray, Error> {
134
+ this.check_frozen()?;
135
+ let (now, ttl) = (duration(ruby, now)?, duration(ruby, ttl)?);
136
+ let value = this.0.borrow_mut().acquire(OwnerId::new(owner.0), now, ttl);
137
+ decision(ruby, value)
138
+ }
139
+ fn renew(
140
+ ruby: &Ruby,
141
+ this: Obj<Self>,
142
+ owner: Count,
143
+ value: Count,
144
+ now: f64,
145
+ ttl: f64,
146
+ ) -> Result<RArray, Error> {
147
+ this.check_frozen()?;
148
+ let (value, now, ttl) = (
149
+ token(ruby, value)?,
150
+ duration(ruby, now)?,
151
+ duration(ruby, ttl)?,
152
+ );
153
+ let value = this
154
+ .0
155
+ .borrow_mut()
156
+ .renew(OwnerId::new(owner.0), value, now, ttl);
157
+ decision(ruby, value)
158
+ }
159
+ fn release(
160
+ ruby: &Ruby,
161
+ this: Obj<Self>,
162
+ owner: Count,
163
+ value: Count,
164
+ now: f64,
165
+ ) -> Result<RArray, Error> {
166
+ this.check_frozen()?;
167
+ let (value, now) = (token(ruby, value)?, duration(ruby, now)?);
168
+ let value = this
169
+ .0
170
+ .borrow_mut()
171
+ .release(OwnerId::new(owner.0), value, now);
172
+ decision(ruby, value)
173
+ }
174
+ }
175
+
176
+ #[magnus::wrap(class = "LeaseMachines::Fence", free_immediately, size)]
177
+ struct Guard(RefCell<Fence>);
178
+
179
+ impl Guard {
180
+ fn new(ruby: &Ruby, highest: Option<Count>) -> Result<Self, Error> {
181
+ Ok(Self(RefCell::new(Fence::new(
182
+ highest.map(|v| token(ruby, v)).transpose()?,
183
+ ))))
184
+ }
185
+ fn accept(ruby: &Ruby, this: Obj<Self>, value: Count) -> Result<bool, Error> {
186
+ this.check_frozen()?;
187
+ let value = token(ruby, value)?;
188
+ Ok(this.0.borrow_mut().accept(value))
189
+ }
190
+ fn highest(&self) -> Option<u64> {
191
+ self.0.borrow().highest().map(FencingToken::value)
192
+ }
193
+ }
194
+
195
+ #[magnus::init]
196
+ fn init(ruby: &Ruby) -> Result<(), Error> {
197
+ let module = ruby.define_module("LeaseMachines")?;
198
+ let grant = module.define_class("Lease", ruby.class_object())?;
199
+ grant.undef_default_alloc_func();
200
+ grant.define_method("owner", method!(Grant::owner, 0))?;
201
+ grant.define_method("token", method!(Grant::token, 0))?;
202
+ grant.define_method("expires_at", method!(Grant::expires_at, 0))?;
203
+ let saved = module.define_class("Checkpoint", ruby.class_object())?;
204
+ saved.undef_default_alloc_func();
205
+ saved.define_method("last_issued", method!(Saved::last_issued, 0))?;
206
+ saved.define_method("observed", method!(Saved::observed, 0))?;
207
+ let authority = module.define_class("Authority", ruby.class_object())?;
208
+ authority.define_singleton_method("new", function!(Authority::new, 0))?;
209
+ authority.define_singleton_method("restore", function!(Authority::restore, 1))?;
210
+ authority.define_method("checkpoint", method!(Authority::checkpoint, 0))?;
211
+ authority.define_method("_acquire", method!(Authority::acquire, 3))?;
212
+ authority.define_method("_renew", method!(Authority::renew, 4))?;
213
+ authority.define_method("_release", method!(Authority::release, 3))?;
214
+ let fence = module.define_class("Fence", ruby.class_object())?;
215
+ fence.define_singleton_method("_new", function!(Guard::new, 1))?;
216
+ fence.define_method("accept", method!(Guard::accept, 1))?;
217
+ fence.define_method("highest", method!(Guard::highest, 0))
218
+ }
@@ -0,0 +1,2 @@
1
+ //! Native Ruby lease decisions; no storage, clocks, or resource I/O.
2
+ mod bindings;
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module LeaseMachines
3
+ VERSION = '0.1.6'
4
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'lease_machines/version'
3
+ require 'lease_machines_native/lease_machines_native'
4
+
5
+ module LeaseMachines
6
+ Decision = Data.define(:status, :lease, :reason)
7
+
8
+ class Authority
9
+ def acquire(owner:, now:, ttl:)
10
+ Decision.new(*_acquire(owner, now, ttl))
11
+ end
12
+
13
+ def renew(owner:, token:, now:, ttl:)
14
+ Decision.new(*_renew(owner, token, now, ttl))
15
+ end
16
+
17
+ def release(owner:, token:, now:)
18
+ Decision.new(*_release(owner, token, now))
19
+ end
20
+
21
+ private :_acquire, :_renew, :_release
22
+ end
23
+
24
+ class Fence
25
+ def self.new(highest: nil) = _new(highest)
26
+ private_class_method :_new
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module LeaseMachines
2
+ VERSION: String
3
+ class Lease
4
+ def owner: () -> Integer
5
+ def token: () -> Integer
6
+ def expires_at: () -> Float
7
+ end
8
+ class Checkpoint
9
+ def last_issued: () -> Integer?
10
+ def observed: () -> Float
11
+ end
12
+ class Decision < Data
13
+ attr_reader status: Symbol
14
+ attr_reader lease: Lease?
15
+ attr_reader reason: Symbol?
16
+ end
17
+ class Authority
18
+ def self.new: () -> instance
19
+ def self.restore: (Checkpoint) -> instance
20
+ def acquire: (owner: Integer, now: Float, ttl: Float) -> Decision
21
+ def renew: (owner: Integer, token: Integer, now: Float, ttl: Float) -> Decision
22
+ def release: (owner: Integer, token: Integer, now: Float) -> Decision
23
+ def checkpoint: () -> Checkpoint
24
+ end
25
+ class Fence
26
+ def self.new: (?highest: Integer?) -> instance
27
+ def accept: (Integer) -> bool
28
+ def highest: () -> Integer?
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lease_machines
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - Abdelkader Boudih
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rb_sys
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.9'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.130
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '0.9'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.9.130
32
+ description: Thin Ruby bindings to the standalone lease-machines core; durability,
33
+ clocks, and resource enforcement belong to the caller.
34
+ email:
35
+ - oss@seuros.com
36
+ executables: []
37
+ extensions:
38
+ - ext/lease_machines_native/extconf.rb
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG.md
42
+ - Cargo.toml
43
+ - LICENSE.txt
44
+ - README.md
45
+ - ext/lease_machines_native/core/CHANGELOG.md
46
+ - ext/lease_machines_native/core/Cargo.toml
47
+ - ext/lease_machines_native/core/README.md
48
+ - ext/lease_machines_native/core/src/authority.rs
49
+ - ext/lease_machines_native/core/src/fencing.rs
50
+ - ext/lease_machines_native/core/src/holder.rs
51
+ - ext/lease_machines_native/core/src/lease.rs
52
+ - ext/lease_machines_native/core/src/lib.rs
53
+ - ext/lease_machines_native/extconf.rb
54
+ - ext/lease_machines_native/ffi/Cargo.toml
55
+ - ext/lease_machines_native/ffi/extconf.rb
56
+ - ext/lease_machines_native/ffi/src/bindings.rs
57
+ - ext/lease_machines_native/ffi/src/lib.rs
58
+ - lib/lease_machines.rb
59
+ - lib/lease_machines/version.rb
60
+ - sig/lease_machines.rbs
61
+ homepage: https://github.com/seuros/lease_machines
62
+ licenses:
63
+ - MIT
64
+ metadata:
65
+ source_code_uri: https://github.com/seuros/lease_machines
66
+ rubygems_mfa_required: 'true'
67
+ cargo_crate_name: lease_machines_native
68
+ cargo_manifest_path: ext/lease_machines_native/ffi/Cargo.toml
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '4.0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 4.0.20
84
+ specification_version: 4
85
+ summary: Native lease lifecycle and fencing decisions for Ruby
86
+ test_files: []