xchan.rb 0.17.2 → 0.19.0
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.
- checksums.yaml +4 -4
- data/README.md +68 -24
- data/lib/xchan/bytes.rb +1 -8
- data/lib/xchan/counter.rb +1 -2
- data/lib/xchan/null_lock.rb +44 -0
- data/lib/xchan/tempfile.rb +23 -17
- data/lib/xchan/unix_socket.rb +12 -35
- data/lib/xchan/version.rb +1 -1
- data/lib/xchan.rb +23 -17
- data/share/xchan.rb/examples/read_operations/1_blocking_read.rb +1 -0
- data/share/xchan.rb/examples/read_operations/2_nonblocking_read.rb +1 -0
- data/share/xchan.rb/examples/serialization/1_serializers.rb +4 -3
- data/share/xchan.rb/examples/socket/{2_options.rb → 1_options.rb} +3 -0
- data/share/xchan.rb/examples/write_operations/1_blocking_write.rb +2 -1
- data/share/xchan.rb/examples/write_operations/2_nonblocking_write.rb +2 -1
- data/test/readme_test.rb +17 -15
- data/test/setup.rb +1 -0
- data/test/xchan_test.rb +1 -1
- data/xchan.rb.gemspec +11 -4
- metadata +26 -20
- data/.github/workflows/tests.yml +0 -26
- data/.gitignore +0 -8
- data/.gitlab-ci.yml +0 -12
- data/.projectile +0 -4
- data/.rubocop.yml +0 -34
- data/.yardopts +0 -4
- data/Gemfile +0 -4
- data/LICENSE +0 -15
- data/Rakefile.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a87a4e61f65ec4df37ecc2b09efd3d9abd4d39e57ac936db27e43ace4e99818d
|
4
|
+
data.tar.gz: 1ac4be7c20abcdee66a441088e1a7bf67b92b96f63b16b3401227bb978afa6de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc46a04ae48123fbf24371bd3438013cfcb070e353993811d10b4ea9d8b63628b577bed465d441fab1d964a0dab4ec9a2040e293c59272faa6c2081819788383
|
7
|
+
data.tar.gz: 267dd70488de1b49c345c49a87c5f28a7bdf0713c20fdeeba2efbef13e2d114960dc021e3b1ec77df90ad3c1189772e3075dff3ce0f2ebfac27b727acc4a56a8
|
data/README.md
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
xchan.rb is an easy to use library for InterProcess
|
4
4
|
Communication (IPC). The library provides a channel
|
5
|
-
that can
|
6
|
-
|
5
|
+
that can help facilitate communication between Ruby
|
6
|
+
processes who have a parent <=> child relationship.
|
7
7
|
|
8
8
|
## Examples
|
9
9
|
|
@@ -11,30 +11,26 @@ with a parent <-> child relationship.
|
|
11
11
|
|
12
12
|
#### Options
|
13
13
|
|
14
|
-
The first argument
|
15
|
-
that
|
16
|
-
in
|
17
|
-
available as `xchan(:pure)
|
18
|
-
|
19
|
-
|
20
|
-
a Ruby object is serialized (on write) or deserialized
|
21
|
-
(on read). The serializers available to choose from
|
22
|
-
are `xchan(:marshal)`, `xchan(:json)`, and `xchan(:yaml)`.
|
23
|
-
The example uses
|
24
|
-
[`Marshal`](https://www.rubydoc.info/stdlib/core/Marshal):
|
14
|
+
The first argument provided to xchan is the serializer
|
15
|
+
that should be used. A channel that will communicate
|
16
|
+
purely in strings (in other words: without serialization)
|
17
|
+
is available as `xchan(:pure)` - otherwise a wide range of
|
18
|
+
serializers are available by default: `xchan(:marshal)`,
|
19
|
+
`xchan(:json)`, and `xchan(:yaml)`.
|
25
20
|
|
26
21
|
```ruby
|
22
|
+
#!/usr/bin/env ruby
|
27
23
|
require "xchan"
|
28
24
|
|
29
25
|
##
|
30
|
-
#
|
26
|
+
# Marshal as the serializer
|
31
27
|
ch = xchan(:marshal)
|
32
28
|
Process.wait fork { ch.send(5) }
|
33
|
-
print "
|
29
|
+
print "#{ch.recv} + 7 = 12", "\n"
|
34
30
|
ch.close
|
35
31
|
|
36
32
|
##
|
37
|
-
#
|
33
|
+
# 5 + 7 = 12
|
38
34
|
```
|
39
35
|
|
40
36
|
### Read operations
|
@@ -49,6 +45,7 @@ blocks. The example performs a read that blocks until
|
|
49
45
|
the parent process writes to the channel:
|
50
46
|
|
51
47
|
```ruby
|
48
|
+
#!/usr/bin/env ruby
|
52
49
|
require "xchan"
|
53
50
|
|
54
51
|
ch = xchan(:marshal)
|
@@ -76,6 +73,7 @@ the method raises `Chan::WaitReadable` when a read from
|
|
76
73
|
blocks:
|
77
74
|
|
78
75
|
```ruby
|
76
|
+
#!/usr/bin/env ruby
|
79
77
|
require "xchan"
|
80
78
|
|
81
79
|
def read(ch)
|
@@ -108,9 +106,10 @@ process, or when a write to
|
|
108
106
|
blocks. The example fills the send buffer:
|
109
107
|
|
110
108
|
```ruby
|
109
|
+
#!/usr/bin/env ruby
|
111
110
|
require "xchan"
|
112
111
|
|
113
|
-
ch = xchan(:marshal,
|
112
|
+
ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
|
114
113
|
sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
|
115
114
|
while ch.bytes_sent <= sndbuf.int
|
116
115
|
ch.send(1)
|
@@ -128,6 +127,7 @@ a lock held by another process, and the method raises
|
|
128
127
|
blocks. The example frees space on the send buffer:
|
129
128
|
|
130
129
|
```ruby
|
130
|
+
#!/usr/bin/env ruby
|
131
131
|
require "xchan"
|
132
132
|
|
133
133
|
def send_nonblock(ch, buf)
|
@@ -141,7 +141,7 @@ rescue Chan::WaitLockable
|
|
141
141
|
retry
|
142
142
|
end
|
143
143
|
|
144
|
-
ch = xchan(:marshal,
|
144
|
+
ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
|
145
145
|
sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
|
146
146
|
while ch.bytes_sent <= sndbuf.int
|
147
147
|
send_nonblock(ch, 1)
|
@@ -151,6 +151,49 @@ end
|
|
151
151
|
# Blocked - free send buffer
|
152
152
|
```
|
153
153
|
|
154
|
+
### Lock
|
155
|
+
|
156
|
+
#### File
|
157
|
+
|
158
|
+
The default lock for a channel is a file lock. The locking mechanism is
|
159
|
+
implemented with the
|
160
|
+
[lockf](https://man.freebsd.org/cgi/man.cgi?query=lockf&apropos=0&sektion=3&manpath=FreeBSD+14.2-RELEASE+and+Ports&arch=default&format=html)
|
161
|
+
function from the C standard library. Nothing special has to be done to
|
162
|
+
use it, and it allows a channel to be safely accessed across multiple
|
163
|
+
processes:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
#!/usr/bin/env ruby
|
167
|
+
require "xchan"
|
168
|
+
|
169
|
+
ch = xchan(:marshal, lock: :file)
|
170
|
+
5.times.map do
|
171
|
+
fork do
|
172
|
+
ch.send(5)
|
173
|
+
end
|
174
|
+
end.each { Process.wait(_1) }
|
175
|
+
```
|
176
|
+
|
177
|
+
#### Null
|
178
|
+
|
179
|
+
The null lock is the same as using no lock at all. The null lock is
|
180
|
+
implemented as a collection of no-op operations. The null lock is
|
181
|
+
implemented in the
|
182
|
+
[Chan::NullLock](https://0x1eef.github,io/x/xchan.rb/Chan/NullLock.html)
|
183
|
+
class, and in certain situations, it can be useful and preferable
|
184
|
+
to using a file lock:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
#!/usr/bin/env ruby
|
188
|
+
require "xchan"
|
189
|
+
|
190
|
+
ch = xchan(:marshal, lock: :null)
|
191
|
+
fork do
|
192
|
+
ch.send(5)
|
193
|
+
end
|
194
|
+
Process.wait
|
195
|
+
```
|
196
|
+
|
154
197
|
### Socket
|
155
198
|
|
156
199
|
#### Options
|
@@ -163,6 +206,7 @@ returns the socket used for read operations, and
|
|
163
206
|
returns the socket used for write operations:
|
164
207
|
|
165
208
|
```ruby
|
209
|
+
#!/usr/bin/env ruby
|
166
210
|
require "xchan"
|
167
211
|
ch = xchan(:marshal)
|
168
212
|
|
@@ -184,7 +228,7 @@ print "The maximum size of a single message is: ", sndbuf.int, " bytes.\n"
|
|
184
228
|
## Documentation
|
185
229
|
|
186
230
|
A complete API reference is available at
|
187
|
-
[0x1eef.github.io/x/xchan.rb](https://0x1eef.github.io/x/xchan.rb/)
|
231
|
+
[0x1eef.github.io/x/xchan.rb](https://0x1eef.github.io/x/xchan.rb/)
|
188
232
|
|
189
233
|
## Install
|
190
234
|
|
@@ -194,11 +238,11 @@ xchan.rb can be installed via rubygems.org:
|
|
194
238
|
|
195
239
|
## Sources
|
196
240
|
|
197
|
-
* [
|
198
|
-
* [
|
241
|
+
* [github.com/@0x1eef](https://github.com/0x1eef/xchan.rb#readme)
|
242
|
+
* [gitlab.com/@0x1eef](https://gitlab.com/0x1eef/xchan.rb#about)
|
199
243
|
|
200
|
-
##
|
244
|
+
## License
|
201
245
|
|
202
|
-
[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/)
|
246
|
+
[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/)
|
203
247
|
<br>
|
204
|
-
See [LICENSE](./LICENSE)
|
248
|
+
See [share/xchan.rb/LICENSE](./share/xchan.rb/LICENSE)
|
data/lib/xchan/bytes.rb
CHANGED
@@ -13,7 +13,6 @@ class Chan::Bytes
|
|
13
13
|
##
|
14
14
|
# @param [String] tmpdir
|
15
15
|
# Directory where temporary files are stored
|
16
|
-
#
|
17
16
|
# @return [Chan::Bytes]
|
18
17
|
def initialize(tmpdir)
|
19
18
|
@io = Chan.temporary_file(%w[bytes .json], tmpdir:)
|
@@ -23,10 +22,8 @@ class Chan::Bytes
|
|
23
22
|
|
24
23
|
##
|
25
24
|
# Adds a count to the start of the collection
|
26
|
-
#
|
27
25
|
# @param [Integer] len
|
28
26
|
# The bytesize of an object
|
29
|
-
#
|
30
27
|
# @return [void]
|
31
28
|
def unshift(len)
|
32
29
|
return 0 if len.nil? || len.zero?
|
@@ -38,10 +35,8 @@ class Chan::Bytes
|
|
38
35
|
|
39
36
|
##
|
40
37
|
# Adds a count to the end of the collection
|
41
|
-
#
|
42
38
|
# @param [Integer] len
|
43
39
|
# The bytesize of an object
|
44
|
-
#
|
45
40
|
# @return [void]
|
46
41
|
def push(len)
|
47
42
|
return 0 if len.nil? || len.zero?
|
@@ -53,7 +48,6 @@ class Chan::Bytes
|
|
53
48
|
|
54
49
|
##
|
55
50
|
# Removes a count from the start of the collection
|
56
|
-
#
|
57
51
|
# @return [Integer]
|
58
52
|
# Returns the removed byte count
|
59
53
|
def shift
|
@@ -73,7 +67,6 @@ class Chan::Bytes
|
|
73
67
|
|
74
68
|
##
|
75
69
|
# Close the underlying IO
|
76
|
-
#
|
77
70
|
# @return [void]
|
78
71
|
def close
|
79
72
|
@io.close
|
@@ -95,6 +88,6 @@ class Chan::Bytes
|
|
95
88
|
end
|
96
89
|
|
97
90
|
def deserialize(bytes)
|
98
|
-
JSON.
|
91
|
+
JSON.parse(bytes)
|
99
92
|
end
|
100
93
|
end
|
data/lib/xchan/counter.rb
CHANGED
@@ -10,7 +10,6 @@ class Chan::Counter
|
|
10
10
|
##
|
11
11
|
# @param [String] tmpdir
|
12
12
|
# Directory where temporary files are stored
|
13
|
-
#
|
14
13
|
# @return [Chan::Counter]
|
15
14
|
def initialize(tmpdir)
|
16
15
|
@io = Chan.temporary_file(%w[counter .json], tmpdir:)
|
@@ -57,6 +56,6 @@ class Chan::Counter
|
|
57
56
|
end
|
58
57
|
|
59
58
|
def deserialize(bytes)
|
60
|
-
JSON.
|
59
|
+
JSON.parse(bytes)
|
61
60
|
end
|
62
61
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# {Chan::NullLock Chan::NullLock} is a no-op lock that can be used
|
5
|
+
# instead of the standard file lock when a lock is not needed
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# ch = xchan(:marshal, lock: Chan::NullLock)
|
9
|
+
# ch.send([1,2,3])
|
10
|
+
# # ditto
|
11
|
+
# ch = xchan(:marshal, lock: :null)
|
12
|
+
# ch.send([1,2,3])
|
13
|
+
class Chan::NullLock
|
14
|
+
##
|
15
|
+
# @return [void]
|
16
|
+
# This method is a no-op
|
17
|
+
def self.lock
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# @return [void]
|
22
|
+
# This method is a no-op
|
23
|
+
def self.lock_nonblock
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# @return [void]
|
28
|
+
# This method is a no-op
|
29
|
+
def self.release
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# @return [void]
|
34
|
+
# This method is a no-op
|
35
|
+
def self.close
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# @return [void]
|
40
|
+
# This method always returns false
|
41
|
+
def self.locked?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
data/lib/xchan/tempfile.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "tmpdir"
|
3
5
|
|
4
6
|
##
|
5
7
|
# @private
|
@@ -63,12 +65,12 @@ class Chan::Tempfile < DelegateClass(File)
|
|
63
65
|
#
|
64
66
|
# Related: Tempfile.create.
|
65
67
|
#
|
66
|
-
def initialize(basename="", tmpdir=nil, mode: 0, perm:
|
68
|
+
def initialize(basename = "", tmpdir = nil, mode: 0, perm: 0o600, **kwargs)
|
67
69
|
warn "Tempfile.new doesn't call the given block.", uplevel: 1 if block_given?
|
68
70
|
|
69
71
|
@unlinked = false
|
70
|
-
@mode = mode|File::RDWR|File::CREAT|File::EXCL
|
71
|
-
::Dir::Tmpname.create(basename, tmpdir, **
|
72
|
+
@mode = mode | File::RDWR | File::CREAT | File::EXCL
|
73
|
+
::Dir::Tmpname.create(basename, tmpdir, **kwargs) do |tmpname, n, opts|
|
72
74
|
@tmpfile = File.open(tmpname, @mode, perm, **opts)
|
73
75
|
@perm = perm
|
74
76
|
@opts = opts.freeze
|
@@ -81,7 +83,7 @@ class Chan::Tempfile < DelegateClass(File)
|
|
81
83
|
# Opens or reopens the file with mode "r+".
|
82
84
|
def open
|
83
85
|
_close
|
84
|
-
mode = @mode & ~(File::CREAT|File::EXCL)
|
86
|
+
mode = @mode & ~(File::CREAT | File::EXCL)
|
85
87
|
@tmpfile = File.open(@tmpfile.path, mode, **@opts)
|
86
88
|
__setobj__(@tmpfile)
|
87
89
|
end
|
@@ -97,7 +99,7 @@ class Chan::Tempfile < DelegateClass(File)
|
|
97
99
|
#
|
98
100
|
# If you don't explicitly unlink the temporary file, the removal
|
99
101
|
# will be delayed until the object is finalized.
|
100
|
-
def close(unlink_now=false)
|
102
|
+
def close(unlink_now = false)
|
101
103
|
_close
|
102
104
|
unlink if unlink_now
|
103
105
|
end
|
@@ -153,7 +155,7 @@ class Chan::Tempfile < DelegateClass(File)
|
|
153
155
|
ObjectSpace.undefine_finalizer(self)
|
154
156
|
@unlinked = true
|
155
157
|
end
|
156
|
-
|
158
|
+
alias_method :delete, :unlink
|
157
159
|
|
158
160
|
# Returns the full path name of the temporary file.
|
159
161
|
# This will be nil if #unlink has been called.
|
@@ -170,7 +172,7 @@ class Chan::Tempfile < DelegateClass(File)
|
|
170
172
|
File.size(@tmpfile.path)
|
171
173
|
end
|
172
174
|
end
|
173
|
-
|
175
|
+
alias_method :length, :size
|
174
176
|
|
175
177
|
# :stopdoc:
|
176
178
|
def inspect
|
@@ -190,7 +192,7 @@ class Chan::Tempfile < DelegateClass(File)
|
|
190
192
|
def call(*args)
|
191
193
|
return if @pid != Process.pid
|
192
194
|
|
193
|
-
|
195
|
+
warn "removing #{@tmpfile.path}..." if $DEBUG
|
194
196
|
|
195
197
|
@tmpfile.close
|
196
198
|
begin
|
@@ -198,7 +200,7 @@ class Chan::Tempfile < DelegateClass(File)
|
|
198
200
|
rescue Errno::ENOENT
|
199
201
|
end
|
200
202
|
|
201
|
-
|
203
|
+
warn "done" if $DEBUG
|
202
204
|
end
|
203
205
|
end
|
204
206
|
|
@@ -240,8 +242,8 @@ class Chan::Tempfile < DelegateClass(File)
|
|
240
242
|
# ensure
|
241
243
|
# f.close
|
242
244
|
# end
|
243
|
-
def self.open(
|
244
|
-
tempfile = new(
|
245
|
+
def self.open(*, **)
|
246
|
+
tempfile = new(*, **)
|
245
247
|
|
246
248
|
if block_given?
|
247
249
|
begin
|
@@ -313,10 +315,10 @@ module Chan
|
|
313
315
|
#
|
314
316
|
# Related: Tempfile.new.
|
315
317
|
#
|
316
|
-
def Tempfile.create(basename="", tmpdir=nil, mode: 0, perm:
|
318
|
+
def Tempfile.create(basename = "", tmpdir = nil, mode: 0, perm: 0o600, **kwargs)
|
317
319
|
tmpfile = nil
|
318
|
-
Dir::Tmpname.create(basename, tmpdir, **
|
319
|
-
mode |= File::RDWR|File::CREAT|File::EXCL
|
320
|
+
Dir::Tmpname.create(basename, tmpdir, **kwargs) do |tmpname, n, opts|
|
321
|
+
mode |= File::RDWR | File::CREAT | File::EXCL
|
320
322
|
tmpfile = File.open(tmpname, mode, perm, **opts)
|
321
323
|
end
|
322
324
|
if block_given?
|
@@ -325,7 +327,11 @@ module Chan
|
|
325
327
|
ensure
|
326
328
|
unless tmpfile.closed?
|
327
329
|
if File.identical?(tmpfile, tmpfile.path)
|
328
|
-
unlinked =
|
330
|
+
unlinked = begin
|
331
|
+
File.unlink tmpfile.path
|
332
|
+
rescue
|
333
|
+
nil
|
334
|
+
end
|
329
335
|
end
|
330
336
|
tmpfile.close
|
331
337
|
end
|
data/lib/xchan/unix_socket.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
##
|
4
|
-
# An easy-to-use InterProcess Communication (IPC) library
|
4
|
+
# An easy-to-use InterProcess Communication (IPC) library
|
5
5
|
class Chan::UNIXSocket
|
6
6
|
require "socket"
|
7
7
|
require "lockf"
|
@@ -29,24 +29,22 @@ class Chan::UNIXSocket
|
|
29
29
|
# ch.send([1,2,3])
|
30
30
|
# ch.recv.pop # => 3
|
31
31
|
# ch.close
|
32
|
-
#
|
33
|
-
# @param [Symbol, <#dump, #load>] s
|
32
|
+
# @param [Symbol, <#dump, #load>] serializer
|
34
33
|
# The name of a serializer
|
35
|
-
#
|
36
|
-
# @param [Integer] sock_type
|
34
|
+
# @param [Integer] sock
|
37
35
|
# Type of socket (eg `Socket::SOCK_STREAM`)
|
38
|
-
#
|
39
36
|
# @param [String] tmpdir
|
40
37
|
# Directory where temporary files can be stored
|
41
|
-
#
|
38
|
+
# @param [Symbol, <Lock::File, Chan::NullLock>] lock
|
39
|
+
# The name of a lock, or an instance of `Lock::File`, or {Chan::NullLock Chan::NullLock}
|
42
40
|
# @return [Chan::UNIXSocket]
|
43
41
|
# Returns an instance of {Chan::UNIXSocket Chan::UNIXSocket}
|
44
|
-
def initialize(
|
45
|
-
@s = Chan.
|
46
|
-
@r, @w = ::UNIXSocket.pair(
|
42
|
+
def initialize(serializer, sock: Socket::SOCK_DGRAM, tmpdir: Dir.tmpdir, lock: :file)
|
43
|
+
@s = Chan.serializers[serializer]&.call || serializer
|
44
|
+
@r, @w = ::UNIXSocket.pair(sock)
|
47
45
|
@bytes = Chan::Bytes.new(tmpdir)
|
48
46
|
@counter = Chan::Counter.new(tmpdir)
|
49
|
-
@lock =
|
47
|
+
@lock = Chan.locks[lock]&.call(tmpdir) || lock
|
50
48
|
end
|
51
49
|
|
52
50
|
##
|
@@ -58,15 +56,13 @@ class Chan::UNIXSocket
|
|
58
56
|
|
59
57
|
##
|
60
58
|
# Closes the channel
|
61
|
-
#
|
62
59
|
# @raise [IOError]
|
63
60
|
# When the channel is closed
|
64
|
-
#
|
65
61
|
# @return [void]
|
66
62
|
def close
|
67
63
|
@lock.lock
|
68
64
|
raise IOError, "channel is closed" if closed?
|
69
|
-
[@r, @w, @bytes, @lock
|
65
|
+
[@r, @w, @bytes, @lock].each(&:close)
|
70
66
|
rescue IOError => ex
|
71
67
|
@lock.release
|
72
68
|
raise(ex)
|
@@ -77,13 +73,10 @@ class Chan::UNIXSocket
|
|
77
73
|
|
78
74
|
##
|
79
75
|
# Performs a blocking write
|
80
|
-
#
|
81
76
|
# @param [Object] object
|
82
77
|
# An object
|
83
|
-
#
|
84
78
|
# @raise [IOError]
|
85
79
|
# When the channel is closed
|
86
|
-
#
|
87
80
|
# @return [Object]
|
88
81
|
# Returns the number of bytes written to the channel
|
89
82
|
def send(object)
|
@@ -95,19 +88,14 @@ class Chan::UNIXSocket
|
|
95
88
|
|
96
89
|
##
|
97
90
|
# Performs a non-blocking write
|
98
|
-
#
|
99
91
|
# @param [Object] object
|
100
92
|
# An object
|
101
|
-
#
|
102
93
|
# @raise [IOError]
|
103
94
|
# When the channel is closed
|
104
|
-
#
|
105
95
|
# @raise [Chan::WaitWritable]
|
106
96
|
# When a write to {#w} blocks
|
107
|
-
#
|
108
97
|
# @raise [Chan::WaitLockable]
|
109
98
|
# When a write blocks because of a lock held by another process
|
110
|
-
#
|
111
99
|
# @return [Integer, nil]
|
112
100
|
# Returns the number of bytes written to the channel
|
113
101
|
def send_nonblock(object)
|
@@ -133,10 +121,8 @@ class Chan::UNIXSocket
|
|
133
121
|
|
134
122
|
##
|
135
123
|
# Performs a blocking read
|
136
|
-
#
|
137
124
|
# @raise [IOError]
|
138
125
|
# When the channel is closed
|
139
|
-
#
|
140
126
|
# @return [Object]
|
141
127
|
# Returns an object from the channel
|
142
128
|
def recv
|
@@ -151,16 +137,12 @@ class Chan::UNIXSocket
|
|
151
137
|
|
152
138
|
##
|
153
139
|
# Performs a non-blocking read
|
154
|
-
#
|
155
140
|
# @raise [IOError]
|
156
141
|
# When the channel is closed
|
157
|
-
#
|
158
142
|
# @raise [Chan::WaitReadable]
|
159
143
|
# When a read from {#r} blocks
|
160
|
-
#
|
161
144
|
# @raise [Chan::WaitLockable]
|
162
145
|
# When a read blocks because of a lock held by another process
|
163
|
-
#
|
164
146
|
# @return [Object]
|
165
147
|
# Returns an object from the channel
|
166
148
|
def recv_nonblock
|
@@ -187,10 +169,9 @@ class Chan::UNIXSocket
|
|
187
169
|
|
188
170
|
##
|
189
171
|
# @example
|
190
|
-
# ch = xchan
|
172
|
+
# ch = xchan(:pure)
|
191
173
|
# 1.upto(4) { ch.send(_1) }
|
192
|
-
# ch.to_a.last # => 4
|
193
|
-
#
|
174
|
+
# ch.to_a.last # => "4"
|
194
175
|
# @return [Array<Object>]
|
195
176
|
# Returns the contents of the channel
|
196
177
|
def to_a
|
@@ -241,10 +222,8 @@ class Chan::UNIXSocket
|
|
241
222
|
|
242
223
|
##
|
243
224
|
# Waits for the channel to become readable
|
244
|
-
#
|
245
225
|
# @param [Float, Integer, nil] s
|
246
226
|
# The number of seconds to wait. Waits indefinitely with no arguments.
|
247
|
-
#
|
248
227
|
# @return [Chan::UNIXSocket, nil]
|
249
228
|
# Returns self when the channel is readable, otherwise returns nil
|
250
229
|
def wait_readable(s = nil)
|
@@ -253,10 +232,8 @@ class Chan::UNIXSocket
|
|
253
232
|
|
254
233
|
##
|
255
234
|
# Waits for the channel to become writable
|
256
|
-
#
|
257
235
|
# @param [Float, Integer, nil] s
|
258
236
|
# The number of seconds to wait. Waits indefinitely with no arguments.
|
259
|
-
#
|
260
237
|
# @return [Chan::UNIXSocket, nil]
|
261
238
|
# Returns self when the channel is writable, otherwise returns nil
|
262
239
|
def wait_writable(s = nil)
|
data/lib/xchan/version.rb
CHANGED
data/lib/xchan.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
module Chan
|
4
4
|
require_relative "xchan/version"
|
5
5
|
require_relative "xchan/unix_socket"
|
6
|
+
require_relative "xchan/null_lock"
|
6
7
|
require_relative "xchan/tempfile"
|
7
8
|
|
8
9
|
WaitReadable = Class.new(IO::EAGAINWaitReadable)
|
@@ -10,17 +11,14 @@ module Chan
|
|
10
11
|
WaitLockable = Class.new(Errno::EWOULDBLOCK)
|
11
12
|
|
12
13
|
##
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# can be useful when you want to communicate
|
17
|
-
# purely in strings.
|
14
|
+
# Coerces an object to a string for a
|
15
|
+
# channel communicating in raw strings
|
16
|
+
# (in other words: without serialization)
|
18
17
|
#
|
19
18
|
# @example
|
20
19
|
# ch = xchan(:pure)
|
21
|
-
#
|
22
|
-
#
|
23
|
-
# }
|
20
|
+
# fork { ch.send "Hello world" }
|
21
|
+
# Process.wait
|
24
22
|
# puts ch.recv
|
25
23
|
Pure = Class.new do
|
26
24
|
def self.dump(str) = str.to_s
|
@@ -35,10 +33,8 @@ module Chan
|
|
35
33
|
#
|
36
34
|
# @param [String] basename
|
37
35
|
# Basename of the temporary file
|
38
|
-
#
|
39
36
|
# @param [String] tmpdir
|
40
37
|
# Parent directory of the temporary file
|
41
|
-
#
|
42
38
|
# @return [Chan::Tempfile]
|
43
39
|
# Returns an instance of {Chan::Tempfile Chan::Tempfile}
|
44
40
|
def self.temporary_file(basename, tmpdir: Dir.tmpdir)
|
@@ -47,8 +43,8 @@ module Chan
|
|
47
43
|
|
48
44
|
##
|
49
45
|
# @return [Hash<Symbol, Proc>]
|
50
|
-
#
|
51
|
-
def self.
|
46
|
+
# Returns the default serializers
|
47
|
+
def self.serializers
|
52
48
|
{
|
53
49
|
pure: lambda { Pure },
|
54
50
|
marshal: lambda { Marshal },
|
@@ -62,6 +58,16 @@ module Chan
|
|
62
58
|
}
|
63
59
|
}
|
64
60
|
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# @return [Hash<Symbol, Proc>]
|
64
|
+
# Returns the default locks
|
65
|
+
def self.locks
|
66
|
+
{
|
67
|
+
null: lambda { |_tmpdir| Chan::NullLock },
|
68
|
+
file: lambda { |tmpdir| Lock::File.new Chan.temporary_file(%w[xchan lock], tmpdir:) }
|
69
|
+
}
|
70
|
+
end
|
65
71
|
end
|
66
72
|
|
67
73
|
module Kernel
|
@@ -71,12 +77,12 @@ module Kernel
|
|
71
77
|
# ch.send([1,2,3])
|
72
78
|
# ch.recv.pop # => 3
|
73
79
|
# ch.close
|
74
|
-
#
|
75
|
-
# @param
|
76
|
-
# @param sock_type (see Chan::UNIXSocket#initialize)
|
80
|
+
# @param serializer (see Chan::UNIXSocket#initialize)
|
81
|
+
# @param sock (see Chan::UNIXSocket#initialize)
|
77
82
|
# @param tmpdir (see Chan::UNIXSocket#initialize)
|
83
|
+
# @param lock (see Chan::UNIXSocket#initialize)
|
78
84
|
# @return (see Chan::UNIXSocket#initialize)
|
79
|
-
def xchan(s,
|
80
|
-
Chan::UNIXSocket.new(s,
|
85
|
+
def xchan(s, ...)
|
86
|
+
Chan::UNIXSocket.new(s, ...)
|
81
87
|
end
|
82
88
|
end
|
@@ -1,14 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative "../setup"
|
4
5
|
require "xchan"
|
5
6
|
|
6
7
|
##
|
7
|
-
#
|
8
|
+
# Marshal as the serializer
|
8
9
|
ch = xchan(:marshal)
|
9
10
|
Process.wait fork { ch.send(5) }
|
10
|
-
print "
|
11
|
+
print "#{ch.recv} + 7 = 12", "\n"
|
11
12
|
ch.close
|
12
13
|
|
13
14
|
##
|
14
|
-
#
|
15
|
+
# 5 + 7 = 12
|
@@ -1,9 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative "../setup"
|
4
5
|
require "xchan"
|
5
6
|
|
6
|
-
ch = xchan(:marshal,
|
7
|
+
ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
|
7
8
|
sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
|
8
9
|
while ch.bytes_sent <= sndbuf.int
|
9
10
|
ch.send(1)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative "../setup"
|
@@ -14,7 +15,7 @@ rescue Chan::WaitLockable
|
|
14
15
|
retry
|
15
16
|
end
|
16
17
|
|
17
|
-
ch = xchan(:marshal,
|
18
|
+
ch = xchan(:marshal, sock: Socket::SOCK_STREAM)
|
18
19
|
sndbuf = ch.w.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
|
19
20
|
while ch.bytes_sent <= sndbuf.int
|
20
21
|
send_nonblock(ch, 1)
|
data/test/readme_test.rb
CHANGED
@@ -1,42 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "setup"
|
4
|
-
require "test
|
4
|
+
require "test-cmd"
|
5
5
|
|
6
6
|
class Chan::ReadmeTest < Test::Unit::TestCase
|
7
7
|
def test_serialization_1_serializers
|
8
|
-
assert_equal "
|
9
|
-
readme_example("serialization/1_serializers.rb").stdout
|
8
|
+
assert_equal "5 + 7 = 12\n",
|
9
|
+
cmd("ruby", readme_example("serialization/1_serializers.rb")).stdout
|
10
10
|
end
|
11
11
|
|
12
12
|
def test_read_operations_1_blocking_read
|
13
13
|
r = 'Send a random number \(from parent process\)\s*' \
|
14
14
|
'Received random number \(child process\): \d+'
|
15
15
|
assert_match Regexp.new(r),
|
16
|
-
readme_example("read_operations/1_blocking_read.rb")
|
17
|
-
|
16
|
+
cmd("ruby", readme_example("read_operations/1_blocking_read.rb"))
|
17
|
+
.stdout
|
18
|
+
.tr("\n", " ")
|
18
19
|
end
|
19
20
|
|
20
21
|
def test_write_operations_2_non_blocking_write
|
21
22
|
assert_equal ["Blocked - free send buffer\n"],
|
22
|
-
readme_example("write_operations/2_nonblocking_write.rb")
|
23
|
-
|
24
|
-
|
23
|
+
cmd("ruby", readme_example("write_operations/2_nonblocking_write.rb"))
|
24
|
+
.stdout
|
25
|
+
.each_line
|
26
|
+
.uniq
|
25
27
|
end
|
26
28
|
|
27
29
|
def test_socket_2_options
|
28
|
-
r = 'The read buffer can contain a maximum of: \d{1,
|
29
|
-
'The maximum size of a single message is: \d{1,
|
30
|
+
r = 'The read buffer can contain a maximum of: \d{1,7} bytes.\s*' \
|
31
|
+
'The maximum size of a single message is: \d{1,7} bytes.\s*'
|
30
32
|
assert_match Regexp.new(r),
|
31
|
-
readme_example("socket/
|
32
|
-
|
33
|
+
cmd("ruby", readme_example("socket/1_options.rb"))
|
34
|
+
.stdout
|
35
|
+
.tr("\n", " ")
|
33
36
|
end
|
34
37
|
|
35
38
|
private
|
36
39
|
|
37
40
|
def readme_example(path)
|
38
|
-
|
39
|
-
|
40
|
-
cmd "bundle exec ruby #{example}"
|
41
|
+
dir = File.join(Dir.getwd, "share", "xchan.rb", "examples")
|
42
|
+
File.join(dir, path)
|
41
43
|
end
|
42
44
|
end
|
data/test/setup.rb
CHANGED
data/test/xchan_test.rb
CHANGED
@@ -205,7 +205,7 @@ class Chan::TemporaryFileTest < Chan::Test
|
|
205
205
|
end
|
206
206
|
|
207
207
|
def test_temporary_file_path
|
208
|
-
assert_match %r
|
208
|
+
assert_match %r{#{Regexp.escape(Dir.tmpdir)}/foobar[a-zA-Z0-9-]+\.txt},
|
209
209
|
file.to_path
|
210
210
|
ensure
|
211
211
|
file.close
|
data/xchan.rb.gemspec
CHANGED
@@ -8,15 +8,22 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.homepage = "https://github.com/0x1eef/xchan.rb#readme"
|
9
9
|
gem.version = Chan::VERSION
|
10
10
|
gem.licenses = ["0BSD"]
|
11
|
-
gem.files =
|
11
|
+
gem.files = Dir[
|
12
|
+
"README.md", "LICENSE",
|
13
|
+
"share/xchan.rb/**/*.rb",
|
14
|
+
"lib/*.rb", "lib/**/*.rb",
|
15
|
+
"test/*.rb", "test/**/*.rb",
|
16
|
+
"xchan.rb.gemspec"
|
17
|
+
]
|
12
18
|
gem.require_paths = ["lib"]
|
13
19
|
gem.summary = "An easy to use InterProcess Communication (IPC) library"
|
14
20
|
gem.description = gem.summary
|
15
|
-
gem.add_runtime_dependency "lockf.rb", "~> 1
|
21
|
+
gem.add_runtime_dependency "lockf.rb", "~> 2.1"
|
16
22
|
gem.add_development_dependency "test-unit", "~> 3.5.7"
|
17
23
|
gem.add_development_dependency "yard", "~> 0.9"
|
18
|
-
gem.add_development_dependency "
|
24
|
+
gem.add_development_dependency "kramdown", "~> 2.5"
|
19
25
|
gem.add_development_dependency "standard", "~> 1.13"
|
20
|
-
gem.add_development_dependency "test-cmd.rb", "~> 0.
|
26
|
+
gem.add_development_dependency "test-cmd.rb", "~> 0.12.4"
|
21
27
|
gem.add_development_dependency "rake", "~> 13.1"
|
28
|
+
gem.add_development_dependency "irb", "~> 1.14"
|
22
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xchan.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- '0x1eef'
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lockf.rb
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
19
|
+
version: '2.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '2.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: test-unit
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0.9'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: kramdown
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '2.5'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '2.5'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: standard
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 0.12.4
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 0.12.4
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '13.1'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: irb
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.14'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.14'
|
111
125
|
description: An easy to use InterProcess Communication (IPC) library
|
112
126
|
email:
|
113
127
|
- 0x1eef@protonmail.com
|
@@ -115,19 +129,11 @@ executables: []
|
|
115
129
|
extensions: []
|
116
130
|
extra_rdoc_files: []
|
117
131
|
files:
|
118
|
-
- ".github/workflows/tests.yml"
|
119
|
-
- ".gitignore"
|
120
|
-
- ".gitlab-ci.yml"
|
121
|
-
- ".projectile"
|
122
|
-
- ".rubocop.yml"
|
123
|
-
- ".yardopts"
|
124
|
-
- Gemfile
|
125
|
-
- LICENSE
|
126
132
|
- README.md
|
127
|
-
- Rakefile.rb
|
128
133
|
- lib/xchan.rb
|
129
134
|
- lib/xchan/bytes.rb
|
130
135
|
- lib/xchan/counter.rb
|
136
|
+
- lib/xchan/null_lock.rb
|
131
137
|
- lib/xchan/tempfile.rb
|
132
138
|
- lib/xchan/unix_socket.rb
|
133
139
|
- lib/xchan/version.rb
|
@@ -135,7 +141,7 @@ files:
|
|
135
141
|
- share/xchan.rb/examples/read_operations/2_nonblocking_read.rb
|
136
142
|
- share/xchan.rb/examples/serialization/1_serializers.rb
|
137
143
|
- share/xchan.rb/examples/setup.rb
|
138
|
-
- share/xchan.rb/examples/socket/
|
144
|
+
- share/xchan.rb/examples/socket/1_options.rb
|
139
145
|
- share/xchan.rb/examples/stress_tests/1_parallel_access_stress_test.rb
|
140
146
|
- share/xchan.rb/examples/write_operations/1_blocking_write.rb
|
141
147
|
- share/xchan.rb/examples/write_operations/2_nonblocking_write.rb
|
@@ -162,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
168
|
- !ruby/object:Gem::Version
|
163
169
|
version: '0'
|
164
170
|
requirements: []
|
165
|
-
rubygems_version: 3.5.
|
171
|
+
rubygems_version: 3.5.23
|
166
172
|
signing_key:
|
167
173
|
specification_version: 4
|
168
174
|
summary: An easy to use InterProcess Communication (IPC) library
|
data/.github/workflows/tests.yml
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
name: xchan.rb
|
2
|
-
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches: [ main ]
|
6
|
-
pull_request:
|
7
|
-
branches: [ main ]
|
8
|
-
|
9
|
-
jobs:
|
10
|
-
specs:
|
11
|
-
strategy:
|
12
|
-
fail-fast: false
|
13
|
-
matrix:
|
14
|
-
os: [ubuntu-latest, macos-latest]
|
15
|
-
ruby: [3.1, 3.2, 3.3]
|
16
|
-
runs-on: ${{ matrix.os }}
|
17
|
-
steps:
|
18
|
-
- uses: actions/checkout@v2
|
19
|
-
- uses: ruby/setup-ruby@v1
|
20
|
-
with:
|
21
|
-
ruby-version: ${{ matrix.ruby }}
|
22
|
-
- run: bundle install
|
23
|
-
- run: SERIALIZER=marshal bundle exec rake
|
24
|
-
- run: SERIALIZER=json bundle exec rake
|
25
|
-
- run: SERIALIZER=yaml bundle exec rake
|
26
|
-
- run: SERIALIZER=pure bundle exec rake
|
data/.gitignore
DELETED
data/.gitlab-ci.yml
DELETED
data/.projectile
DELETED
data/.rubocop.yml
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Plugins
|
3
|
-
require:
|
4
|
-
- standard
|
5
|
-
|
6
|
-
##
|
7
|
-
# Defaults: standard-rb
|
8
|
-
inherit_gem:
|
9
|
-
standard: config/base.yml
|
10
|
-
|
11
|
-
##
|
12
|
-
# All cops
|
13
|
-
AllCops:
|
14
|
-
TargetRubyVersion: 3.2
|
15
|
-
Include:
|
16
|
-
- lib/*.rb
|
17
|
-
- lib/**/*.rb
|
18
|
-
- test/*_test.rb
|
19
|
-
|
20
|
-
##
|
21
|
-
# Enabled
|
22
|
-
Style/FrozenStringLiteralComment:
|
23
|
-
Enabled: true
|
24
|
-
|
25
|
-
##
|
26
|
-
# Disabled
|
27
|
-
Layout/ArgumentAlignment:
|
28
|
-
Enabled: false
|
29
|
-
Layout/MultilineMethodCallIndentation:
|
30
|
-
Enabled: false
|
31
|
-
Layout/EmptyLineBetweenDefs:
|
32
|
-
Enabled: false
|
33
|
-
Style/TrivialAccessors:
|
34
|
-
Enabled: false
|
data/.yardopts
DELETED
data/Gemfile
DELETED
data/LICENSE
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
Copyright (C) 2023 by 0x1eef <0x1eef@protonmail.com>
|
2
|
-
|
3
|
-
Permission to use, copy, modify, and/or distribute this
|
4
|
-
software for any purpose with or without fee is hereby
|
5
|
-
granted.
|
6
|
-
|
7
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
|
8
|
-
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
9
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
|
10
|
-
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
11
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
12
|
-
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
13
|
-
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
14
|
-
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
15
|
-
OF THIS SOFTWARE.
|