winipc 0.1.0 → 0.1.1
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 +11 -154
- data/lib/winipc.rb +4 -338
- metadata +12 -86
- data/CHANGELOG.md +0 -29
- data/ext/winipc/extconf.rb +0 -21
- data/ext/winipc/winipc.c +0 -1246
- data/lib/winipc/version.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '01972cb47231455db57df188c471e00f7ace66e3721da7ba411125d17acf1760'
|
|
4
|
+
data.tar.gz: 9aa7d8e1e0153cff4167379f023cf8cedea3703228003de14b763889b99f9a61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a03d3557987c8c38cabb115e711486e4787cfe339d776867f69c3d721b1c418c7d30c6c1fadab525fec493dfceed5b1ffcd4744f7705cb88608a76dd94a5663
|
|
7
|
+
data.tar.gz: 7bcd9c1c5db77486b3a9c9b031ada85f96f0998e2c5a201d7f948fd4124b45f8ba62f9af5a6742e0e87ffaa0190f8ed9efa912c7aeb6eb869761f952b6fa920d
|
data/README.md
CHANGED
|
@@ -1,154 +1,11 @@
|
|
|
1
|
-
# winipc
|
|
2
|
-
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **Shared memory** — pagefile-backed named regions (`CreateFileMapping`), with
|
|
13
|
-
bounds-checked reads and writes.
|
|
14
|
-
- **Named synchronization** — cross-process **Mutex**, **Event**, and
|
|
15
|
-
**Semaphore**, with auto-releasing block forms.
|
|
16
|
-
|
|
17
|
-
It is **secure by default**: pipe servers reject remote clients and restrict the
|
|
18
|
-
pipe to the current user (closing the documented default ACL that grants
|
|
19
|
-
`Everyone` read), the first instance is created with
|
|
20
|
-
`FILE_FLAG_FIRST_PIPE_INSTANCE` so a squatter can't pre-own the name, and no
|
|
21
|
-
handle is inheritable.
|
|
22
|
-
|
|
23
|
-
## Requirements
|
|
24
|
-
|
|
25
|
-
- **Windows** with a native **MSVC (mswin)** Ruby. Not supported on MinGW/UCRT.
|
|
26
|
-
- Visual Studio 2017+ / Build Tools with the **Desktop development with C++** workload.
|
|
27
|
-
|
|
28
|
-
## Install
|
|
29
|
-
|
|
30
|
-
```sh
|
|
31
|
-
gem install winipc
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Named pipes
|
|
35
|
-
|
|
36
|
-
```ruby
|
|
37
|
-
require "winipc"
|
|
38
|
-
|
|
39
|
-
# Server (one process)
|
|
40
|
-
Winipc::Pipe.listen("myapp/control") do |server|
|
|
41
|
-
server.serve do |conn| # accept + yield + close, in a loop
|
|
42
|
-
request = conn.read(4096)
|
|
43
|
-
conn.write("ack: #{request}")
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Client (another process)
|
|
48
|
-
Winipc::Pipe.connect("myapp/control", timeout: 5) do |c|
|
|
49
|
-
c.write("hello")
|
|
50
|
-
puts c.read(4096) # => "ack: hello"
|
|
51
|
-
end
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
`read` returns `nil` at a clean EOF (peer closed), exactly like `IO#read`; a peer
|
|
55
|
-
dying mid-stream raises `Winipc::BrokenPipe`. **Message mode** preserves message
|
|
56
|
-
boundaries:
|
|
57
|
-
|
|
58
|
-
```ruby
|
|
59
|
-
Winipc::Pipe.listen("svc", mode: :message) { |s| c = s.accept; c.read_message }
|
|
60
|
-
Winipc::Pipe.connect("svc", mode: :message) { |c| c.write_message("one whole message") }
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Cooperative under winloop
|
|
64
|
-
|
|
65
|
-
Because pipe handles are overlapped and the blocking calls release the GVL, a
|
|
66
|
-
server and many clients can run as fibers on a single IOCP loop:
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
require "winloop"
|
|
70
|
-
Winloop.run do
|
|
71
|
-
Fiber.schedule { Winipc::Pipe.listen("svc") { |s| s.serve { |c| c.write(c.read(1024)) } } }
|
|
72
|
-
Fiber.schedule { Winipc::Pipe.connect("svc") { |c| c.write("hi"); c.read(1024) } }
|
|
73
|
-
end
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Under a scheduler, each blocking winipc call is offloaded to a worker thread and
|
|
77
|
-
the calling fiber parks, so the loop keeps serving other fibers. With no
|
|
78
|
-
scheduler, the same calls just block the calling thread (releasing the GVL).
|
|
79
|
-
|
|
80
|
-
## Shared memory
|
|
81
|
-
|
|
82
|
-
```ruby
|
|
83
|
-
# Process A
|
|
84
|
-
shm = Winipc::SharedMemory.create("myapp/buffer", 4096)
|
|
85
|
-
shm.write(0, "shared payload")
|
|
86
|
-
|
|
87
|
-
# Process B
|
|
88
|
-
shm = Winipc::SharedMemory.open("myapp/buffer")
|
|
89
|
-
shm.read(0, 14) # => "shared payload"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Reads and writes are bounds-checked (`Winipc::RangeError` outside the region).
|
|
93
|
-
Pair shared memory with a named `Event` to hand off deterministically rather
|
|
94
|
-
than polling.
|
|
95
|
-
|
|
96
|
-
> **Size note:** the creator's `#size` is exactly the requested size; an
|
|
97
|
-
> **opener**'s `#size` is the page-rounded mapped region (Windows rounds a
|
|
98
|
-
> mapping up to the page boundary and doesn't record the logical size), so an
|
|
99
|
-
> opener may read/write within the rounded region. If the exact length matters,
|
|
100
|
-
> carry it out-of-band (e.g. store it in the first bytes of the region).
|
|
101
|
-
|
|
102
|
-
## Named synchronization
|
|
103
|
-
|
|
104
|
-
```ruby
|
|
105
|
-
# Cross-process mutex
|
|
106
|
-
m = Winipc::Mutex.create("myapp/lock")
|
|
107
|
-
m.synchronize { critical_section } # always released, even on exception
|
|
108
|
-
|
|
109
|
-
# Event (one process waits, another signals)
|
|
110
|
-
ev = Winipc::Event.create("myapp/ready", manual_reset: true)
|
|
111
|
-
ev.wait(timeout: 10) # => true (signaled) / false (timed out)
|
|
112
|
-
ev.signal
|
|
113
|
-
|
|
114
|
-
# Counting semaphore
|
|
115
|
-
sem = Winipc::Semaphore.create("myapp/slots", initial: 3, maximum: 3)
|
|
116
|
-
sem.synchronize { use_a_slot }
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
`Mutex` is owned by the acquiring **thread**, so its waits are not offloaded to a
|
|
120
|
-
worker (they release the GVL but, under a fiber scheduler, block the loop for the
|
|
121
|
-
duration). Use `Event`/`Semaphore` — which any thread may wait on and signal —
|
|
122
|
-
for fiber-cooperative coordination. An abandoned mutex (its holder died) is
|
|
123
|
-
surfaced as `Winipc::Abandoned` / `#abandoned?` rather than silently ignored.
|
|
124
|
-
|
|
125
|
-
## Errors
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
StandardError
|
|
129
|
-
└─ Winipc::Error
|
|
130
|
-
├─ Winipc::OSError # a Windows API failed; #code is GetLastError
|
|
131
|
-
│ ├─ Winipc::TimeoutError ├─ Winipc::BrokenPipe ├─ Winipc::NotOwner
|
|
132
|
-
│ ├─ Winipc::NotFound ├─ Winipc::PipeBusy └─ Winipc::WouldExceedMax
|
|
133
|
-
│ ├─ Winipc::Exists ├─ Winipc::Canceled
|
|
134
|
-
│ └─ Winipc::AccessDenied
|
|
135
|
-
├─ Winipc::ModeError # byte/message-mode misuse
|
|
136
|
-
├─ Winipc::RangeError # shared-memory access out of bounds
|
|
137
|
-
├─ Winipc::Closed # operating on a closed object
|
|
138
|
-
└─ Winipc::Abandoned # a mutex's previous owner died holding it
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Notes
|
|
142
|
-
|
|
143
|
-
- **Names** are bare (`"myapp/control"`); winipc maps them to `\\.\pipe\…` for
|
|
144
|
-
pipes and the `Local\` kernel namespace for shared memory / sync objects (pass
|
|
145
|
-
`scope: :global` for the `Global\` namespace, which may need privileges).
|
|
146
|
-
- Every kernel handle lives in a wrapper whose finalizer closes it, so a
|
|
147
|
-
forgotten `#close` never leaks — but closing explicitly is good manners.
|
|
148
|
-
- Blocking calls (`accept`, `read`, `write`, `wait`/`lock`/`acquire`) release the
|
|
149
|
-
GVL and are interruptible (`Thread#kill`, `Ctrl-C`, `Timeout`).
|
|
150
|
-
- **Windows/MSVC only.** Links `kernel32` + `advapi32`, built with `cl.exe`.
|
|
151
|
-
|
|
152
|
-
## License
|
|
153
|
-
|
|
154
|
-
[MIT](LICENSE.txt).
|
|
1
|
+
# winipc (discontinued)
|
|
2
|
+
|
|
3
|
+
**winipc is no longer maintained.** This final release (0.1.1) contains no
|
|
4
|
+
code: installing it compiles nothing, and `require "winipc"` raises a
|
|
5
|
+
`LoadError` explaining the discontinuation.
|
|
6
|
+
|
|
7
|
+
Use UNIXSocket (supported on Windows 10+), or win32-mmap / win32-event / win32-mutex instead.
|
|
8
|
+
|
|
9
|
+
Earlier releases remain on RubyGems only because RubyGems does not allow
|
|
10
|
+
versions older than 30 days to be yanked. They are unmaintained and not
|
|
11
|
+
recommended.
|
data/lib/winipc.rb
CHANGED
|
@@ -1,338 +1,4 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# winipc — Windows local IPC for Ruby: named pipes, shared memory, and named
|
|
7
|
-
# synchronization objects, with a safe-by-default API.
|
|
8
|
-
#
|
|
9
|
-
# # named pipe, server side
|
|
10
|
-
# Winipc::Pipe.listen("myapp/control") do |server|
|
|
11
|
-
# conn = server.accept
|
|
12
|
-
# puts conn.read(1024)
|
|
13
|
-
# conn.write("ack")
|
|
14
|
-
# conn.close
|
|
15
|
-
# end
|
|
16
|
-
#
|
|
17
|
-
# # client side (another process)
|
|
18
|
-
# Winipc::Pipe.connect("myapp/control") do |c|
|
|
19
|
-
# c.write("hello")
|
|
20
|
-
# c.read(1024)
|
|
21
|
-
# end
|
|
22
|
-
module Winipc
|
|
23
|
-
# ---- Win32 flag values (verified) --------------------------------------
|
|
24
|
-
PIPE_ACCESS_INBOUND = 0x00000001
|
|
25
|
-
PIPE_ACCESS_OUTBOUND = 0x00000002
|
|
26
|
-
PIPE_ACCESS_DUPLEX = 0x00000003
|
|
27
|
-
FILE_FLAG_OVERLAPPED = 0x40000000
|
|
28
|
-
PIPE_TYPE_MESSAGE = 0x00000004
|
|
29
|
-
PIPE_READMODE_MESSAGE = 0x00000002
|
|
30
|
-
PIPE_WAIT = 0x00000000
|
|
31
|
-
PIPE_REJECT_REMOTE_CLIENTS = 0x00000008
|
|
32
|
-
PIPE_UNLIMITED_INSTANCES = 255
|
|
33
|
-
GENERIC_READ = 0x80000000
|
|
34
|
-
GENERIC_WRITE = 0x40000000
|
|
35
|
-
|
|
36
|
-
# A Windows API failure carries the originating error code (GetLastError /
|
|
37
|
-
# the failing return), set on the exception in C.
|
|
38
|
-
class OSError
|
|
39
|
-
def code
|
|
40
|
-
@code
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
module_function
|
|
45
|
-
|
|
46
|
-
# Run a blocking native call cooperatively. Under a Fiber scheduler (e.g.
|
|
47
|
-
# winloop) the call is offloaded to a worker Thread so the calling fiber
|
|
48
|
-
# parks (Thread#value routes through the scheduler) and the event loop keeps
|
|
49
|
-
# serving other fibers; with no scheduler it runs inline (the C call already
|
|
50
|
-
# releases the GVL). On fiber unwind the worker is killed+joined so it can't
|
|
51
|
-
# leak or consume data destined for a later op.
|
|
52
|
-
#
|
|
53
|
-
# Caveat: if the fiber is unwound (e.g. Timeout) in the instant after the
|
|
54
|
-
# worker acquired a resource but before the value was delivered, that
|
|
55
|
-
# acquisition is lost — there is no generic way to hand it back (the same
|
|
56
|
-
# inherent limitation as a cancelled read that already pulled bytes).
|
|
57
|
-
def run_blocking
|
|
58
|
-
sched = Fiber.scheduler
|
|
59
|
-
return yield unless sched
|
|
60
|
-
|
|
61
|
-
worker = Thread.new do
|
|
62
|
-
Thread.current.report_on_exception = false
|
|
63
|
-
yield
|
|
64
|
-
end
|
|
65
|
-
begin
|
|
66
|
-
worker.value
|
|
67
|
-
ensure
|
|
68
|
-
if worker.alive?
|
|
69
|
-
worker.kill
|
|
70
|
-
worker.join
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Normalize a bare pipe name to the \\.\pipe\ namespace (pass a full
|
|
76
|
-
# \\server\pipe\name through unchanged).
|
|
77
|
-
def pipe_path(name)
|
|
78
|
-
s = name.to_s
|
|
79
|
-
s.start_with?("\\\\") ? s : "\\\\.\\pipe\\#{s}"
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Map a bare object name into the Local\ (default) or Global\ namespace.
|
|
83
|
-
def obj_path(name, scope)
|
|
84
|
-
prefix = case scope
|
|
85
|
-
when :local then "Local\\"
|
|
86
|
-
when :global then "Global\\"
|
|
87
|
-
else raise ArgumentError, "scope must be :local or :global, got #{scope.inspect}"
|
|
88
|
-
end
|
|
89
|
-
"#{prefix}#{name}"
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def server_access(direction)
|
|
93
|
-
case direction
|
|
94
|
-
when :duplex then PIPE_ACCESS_DUPLEX
|
|
95
|
-
when :inbound then PIPE_ACCESS_INBOUND
|
|
96
|
-
when :outbound then PIPE_ACCESS_OUTBOUND
|
|
97
|
-
else raise ArgumentError, "direction must be :duplex/:inbound/:outbound"
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
private_class_method :server_access
|
|
101
|
-
|
|
102
|
-
def client_access(direction)
|
|
103
|
-
case direction
|
|
104
|
-
when :duplex then GENERIC_READ | GENERIC_WRITE
|
|
105
|
-
when :read then GENERIC_READ
|
|
106
|
-
when :write then GENERIC_WRITE
|
|
107
|
-
else raise ArgumentError, "direction must be :duplex/:read/:write"
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
private_class_method :client_access
|
|
111
|
-
|
|
112
|
-
def ms_for(timeout)
|
|
113
|
-
return -1 if timeout.nil? # INFINITE (block until signaled / connected)
|
|
114
|
-
|
|
115
|
-
t = Float(timeout)
|
|
116
|
-
raise ArgumentError, "timeout must be non-negative, got #{timeout.inspect}" if t.negative?
|
|
117
|
-
|
|
118
|
-
ms = (t * 1000).round
|
|
119
|
-
# Never collapse a tiny-but-positive wait into a non-blocking poll.
|
|
120
|
-
ms.zero? && t.positive? ? 1 : ms
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
class Pipe
|
|
124
|
-
# Create a named-pipe server. Yields a Listener (auto-closed) if a block is
|
|
125
|
-
# given, else returns it.
|
|
126
|
-
def self.listen(name, mode: :byte, direction: :duplex, max_instances: :unlimited,
|
|
127
|
-
in_buffer: 65_536, out_buffer: 65_536, reject_remote: true, access: :owner)
|
|
128
|
-
open_mode = Winipc.send(:server_access, direction) | FILE_FLAG_OVERLAPPED
|
|
129
|
-
pipe_mode = PIPE_WAIT
|
|
130
|
-
pipe_mode |= PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE if mode == :message
|
|
131
|
-
pipe_mode |= PIPE_REJECT_REMOTE_CLIENTS if reject_remote
|
|
132
|
-
max = max_instances == :unlimited ? PIPE_UNLIMITED_INSTANCES : Integer(max_instances)
|
|
133
|
-
raise ArgumentError, "max_instances must be 1..255" unless (1..255).cover?(max)
|
|
134
|
-
|
|
135
|
-
listener = _listen(Winipc.pipe_path(name), open_mode, pipe_mode, max,
|
|
136
|
-
Integer(in_buffer), Integer(out_buffer), access)
|
|
137
|
-
return listener unless block_given?
|
|
138
|
-
|
|
139
|
-
begin
|
|
140
|
-
yield listener
|
|
141
|
-
ensure
|
|
142
|
-
listener.close
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Connect to a named-pipe server. Yields a Conn (auto-closed) if a block is
|
|
147
|
-
# given, else returns it.
|
|
148
|
-
# +timeout+: nil fails fast if the server isn't connectable now; a number of
|
|
149
|
-
# seconds retries (server busy or not yet up) until it elapses.
|
|
150
|
-
def self.connect(name, mode: :byte, direction: :duplex, timeout: nil)
|
|
151
|
-
access = Winipc.send(:client_access, direction)
|
|
152
|
-
ms =
|
|
153
|
-
if timeout.nil?
|
|
154
|
-
0 # fail fast if the server isn't connectable now
|
|
155
|
-
else
|
|
156
|
-
t = Float(timeout)
|
|
157
|
-
raise ArgumentError, "timeout must be non-negative, got #{timeout.inspect}" if t.negative?
|
|
158
|
-
|
|
159
|
-
msr = (t * 1000).round
|
|
160
|
-
msr.zero? && t.positive? ? 1 : msr # a tiny positive timeout still waits, not fail-fast
|
|
161
|
-
end
|
|
162
|
-
conn = Winipc.run_blocking do
|
|
163
|
-
_connect(Winipc.pipe_path(name), access, mode == :message, ms)
|
|
164
|
-
end
|
|
165
|
-
return conn unless block_given?
|
|
166
|
-
|
|
167
|
-
begin
|
|
168
|
-
yield conn
|
|
169
|
-
ensure
|
|
170
|
-
conn.close
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
class Listener
|
|
175
|
-
# Accept the next client. Blocks (cooperatively under a scheduler). With a
|
|
176
|
-
# timeout, returns nil if none connects in time.
|
|
177
|
-
def accept(timeout: nil)
|
|
178
|
-
Winipc.run_blocking { _accept(Winipc.ms_for(timeout)) }
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Accept clients in a loop, yielding (and closing) each connection.
|
|
182
|
-
def serve
|
|
183
|
-
loop do
|
|
184
|
-
conn = accept
|
|
185
|
-
break if conn.nil?
|
|
186
|
-
|
|
187
|
-
begin
|
|
188
|
-
yield conn
|
|
189
|
-
ensure
|
|
190
|
-
conn.close
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
class Conn
|
|
197
|
-
# Read up to +maxlen+ bytes (byte mode). Returns a binary String, or nil
|
|
198
|
-
# at clean EOF (peer closed).
|
|
199
|
-
def read(maxlen)
|
|
200
|
-
Winipc.run_blocking { _read(maxlen) }
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# Read one whole message (message mode), reassembling across the buffer.
|
|
204
|
-
# Returns a binary String or nil at EOF.
|
|
205
|
-
def read_message(maxlen = 65_536)
|
|
206
|
-
raise ModeError, "winipc: pipe is not in message mode" unless message?
|
|
207
|
-
|
|
208
|
-
Winipc.run_blocking { _read_message(maxlen) }
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Write +bytes+; returns the number of bytes written.
|
|
212
|
-
def write(bytes)
|
|
213
|
-
Winipc.run_blocking { _write(bytes) }
|
|
214
|
-
end
|
|
215
|
-
alias write_message write # in message mode each write is one message
|
|
216
|
-
|
|
217
|
-
def <<(bytes)
|
|
218
|
-
write(bytes)
|
|
219
|
-
self
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
class SharedMemory
|
|
225
|
-
# Create a named, pagefile-backed shared-memory region of +size+ bytes.
|
|
226
|
-
def self.create(name, size, access: :readwrite, scope: :local, security: :owner)
|
|
227
|
-
_create(Winipc.obj_path(name, scope), Integer(size), access != :readonly, security)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
# Open an existing named region.
|
|
231
|
-
def self.open(name, access: :readwrite, scope: :local)
|
|
232
|
-
_open(Winipc.obj_path(name, scope), access != :readonly)
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Flush a range (default: whole region) to backing storage.
|
|
236
|
-
def flush(offset = 0, len = nil)
|
|
237
|
-
_flush(offset, len)
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
class Mutex
|
|
242
|
-
def self.create(name, scope: :local, security: :owner)
|
|
243
|
-
_create(K_MUTEX, Winipc.obj_path(name, scope), nil, nil, security)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def self.open(name, scope: :local)
|
|
247
|
-
_open(K_MUTEX, Winipc.obj_path(name, scope))
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
# NOTE: a Windows mutex is owned by the acquiring THREAD, so Mutex waits are
|
|
251
|
-
# NOT offloaded to a worker (that would acquire on the wrong thread). They
|
|
252
|
-
# release the GVL but, under a fiber scheduler, block the loop for the wait.
|
|
253
|
-
# Use Event/Semaphore for fiber-cooperative coordination.
|
|
254
|
-
def lock(timeout: nil)
|
|
255
|
-
_wait(Winipc.ms_for(timeout)) != :timeout # :ok or :abandoned both acquire
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
def try_lock
|
|
259
|
-
lock(timeout: 0)
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def unlock
|
|
263
|
-
_unlock
|
|
264
|
-
self
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def synchronize(timeout: nil)
|
|
268
|
-
result = _wait(Winipc.ms_for(timeout))
|
|
269
|
-
raise TimeoutError, "winipc: mutex not acquired within timeout" if result == :timeout
|
|
270
|
-
|
|
271
|
-
begin
|
|
272
|
-
if result == :abandoned
|
|
273
|
-
raise Abandoned, "winipc: mutex was abandoned by a dead owner; shared state may be inconsistent"
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
yield
|
|
277
|
-
ensure
|
|
278
|
-
_unlock
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
class Event
|
|
284
|
-
def self.create(name, manual_reset: false, initial: false, scope: :local, security: :owner)
|
|
285
|
-
_create(K_EVENT, Winipc.obj_path(name, scope), manual_reset, initial, security)
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def self.open(name, scope: :local)
|
|
289
|
-
_open(K_EVENT, Winipc.obj_path(name, scope))
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
# Wait for the event to be signaled. Returns true, or false on timeout.
|
|
293
|
-
def wait(timeout: nil)
|
|
294
|
-
Winipc.run_blocking { _wait(Winipc.ms_for(timeout)) } == :ok
|
|
295
|
-
end
|
|
296
|
-
# #signal and #reset are defined in C.
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
class Semaphore
|
|
300
|
-
def self.create(name, initial:, maximum:, scope: :local, security: :owner)
|
|
301
|
-
unless maximum.is_a?(Integer) && maximum > 0 && initial.is_a?(Integer) && initial >= 0 && initial <= maximum
|
|
302
|
-
raise ArgumentError, "require 0 <= initial <= maximum and maximum > 0"
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
_create(K_SEM, Winipc.obj_path(name, scope), initial, maximum, security)
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def self.open(name, scope: :local)
|
|
309
|
-
_open(K_SEM, Winipc.obj_path(name, scope))
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
# Acquire one unit. Returns true, or false on timeout.
|
|
313
|
-
def acquire(timeout: nil)
|
|
314
|
-
Winipc.run_blocking { _wait(Winipc.ms_for(timeout)) } == :ok
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
def try_acquire
|
|
318
|
-
acquire(timeout: 0)
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
# Release +count+ units; returns the previous count.
|
|
322
|
-
def release(count = 1)
|
|
323
|
-
_release(count)
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# Acquire one unit for the duration of the block, then release it. (Use
|
|
327
|
-
# #acquire / #release directly for multi-unit patterns.)
|
|
328
|
-
def synchronize(timeout: nil)
|
|
329
|
-
raise TimeoutError, "winipc: semaphore not acquired within timeout" unless acquire(timeout: timeout)
|
|
330
|
-
|
|
331
|
-
begin
|
|
332
|
-
yield
|
|
333
|
-
ensure
|
|
334
|
-
_release(1)
|
|
335
|
-
end
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
raise LoadError, "winipc is discontinued and this release contains no code. Use UNIXSocket (supported on Windows 10+), or win32-mmap / win32-event / win32-mutex instead." \
|
|
4
|
+
" Earlier winipc releases are unmaintained and not recommended."
|
metadata
CHANGED
|
@@ -1,105 +1,31 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: winipc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ned
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
requirements:
|
|
16
|
-
- - "~>"
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '13.0'
|
|
19
|
-
type: :development
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '13.0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rake-compiler
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '1.2'
|
|
33
|
-
type: :development
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '1.2'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: minitest
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - "~>"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '5.0'
|
|
47
|
-
type: :development
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - "~>"
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '5.0'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: vcvars
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.1'
|
|
61
|
-
- - ">="
|
|
62
|
-
- !ruby/object:Gem::Version
|
|
63
|
-
version: 0.1.1
|
|
64
|
-
type: :development
|
|
65
|
-
prerelease: false
|
|
66
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
67
|
-
requirements:
|
|
68
|
-
- - "~>"
|
|
69
|
-
- !ruby/object:Gem::Version
|
|
70
|
-
version: '0.1'
|
|
71
|
-
- - ">="
|
|
72
|
-
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.1.1
|
|
74
|
-
description: |
|
|
75
|
-
winipc is a native extension that exposes Windows local inter-process
|
|
76
|
-
communication through an ergonomic, safe-by-default Ruby API: duplex named
|
|
77
|
-
pipes (byte and message mode, with a server and a connect-with-retry client),
|
|
78
|
-
pagefile-backed shared memory via named file mappings, and named
|
|
79
|
-
synchronization objects (mutex, event, semaphore). Pipe handles are opened
|
|
80
|
-
for overlapped I/O so they cooperate with a fiber scheduler, and objects are
|
|
81
|
-
created with a restrictive security descriptor by default. Windows MSVC
|
|
82
|
-
(mswin) Ruby only.
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: winipc is discontinued. This final release contains no code and exists
|
|
13
|
+
only so that installing winipc explains the discontinuation instead of building
|
|
14
|
+
an unmaintained native extension.
|
|
83
15
|
executables: []
|
|
84
|
-
extensions:
|
|
85
|
-
- ext/winipc/extconf.rb
|
|
16
|
+
extensions: []
|
|
86
17
|
extra_rdoc_files: []
|
|
87
18
|
files:
|
|
88
|
-
- CHANGELOG.md
|
|
89
19
|
- LICENSE.txt
|
|
90
20
|
- README.md
|
|
91
|
-
- ext/winipc/extconf.rb
|
|
92
|
-
- ext/winipc/winipc.c
|
|
93
21
|
- lib/winipc.rb
|
|
94
|
-
- lib/winipc/version.rb
|
|
95
|
-
homepage: https://github.com/main-path/winipc
|
|
96
22
|
licenses:
|
|
97
23
|
- MIT
|
|
98
24
|
metadata:
|
|
99
|
-
homepage_uri: https://github.com/main-path/winipc
|
|
100
|
-
changelog_uri: https://github.com/main-path/winipc/blob/main/CHANGELOG.md
|
|
101
|
-
bug_tracker_uri: https://github.com/main-path/winipc/issues
|
|
102
25
|
rubygems_mfa_required: 'true'
|
|
26
|
+
post_install_message: winipc is discontinued and this release contains no code. Use
|
|
27
|
+
UNIXSocket (supported on Windows 10+), or win32-mmap / win32-event / win32-mutex
|
|
28
|
+
instead.
|
|
103
29
|
rdoc_options: []
|
|
104
30
|
require_paths:
|
|
105
31
|
- lib
|
|
@@ -107,7 +33,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
107
33
|
requirements:
|
|
108
34
|
- - ">="
|
|
109
35
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '
|
|
36
|
+
version: '2.0'
|
|
111
37
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
38
|
requirements:
|
|
113
39
|
- - ">="
|
|
@@ -116,6 +42,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
116
42
|
requirements: []
|
|
117
43
|
rubygems_version: 3.6.9
|
|
118
44
|
specification_version: 4
|
|
119
|
-
summary:
|
|
120
|
-
|
|
45
|
+
summary: Discontinued. Use UNIXSocket (supported on Windows 10+), or win32-mmap /
|
|
46
|
+
win32-event / win32-mutex instead.
|
|
121
47
|
test_files: []
|
data/CHANGELOG.md
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## [0.1.0] - 2026-06-01
|
|
4
|
-
|
|
5
|
-
Initial release.
|
|
6
|
-
|
|
7
|
-
- **Named pipes** — `Winipc::Pipe.listen` (server with `#accept`/`#serve`) and
|
|
8
|
-
`Winipc::Pipe.connect` (connect-with-retry client); a duplex `Conn` with
|
|
9
|
-
`#read`/`#write` (byte mode) and `#read_message`/`#write_message` (message
|
|
10
|
-
mode). Overlapped I/O throughout: blocking calls release the GVL and are
|
|
11
|
-
cancelable, and run cooperatively under a fiber scheduler (winloop) by
|
|
12
|
-
offloading to a worker thread. The listener always keeps a pending instance so
|
|
13
|
-
clients never see a between-accepts gap.
|
|
14
|
-
- **Shared memory** — `Winipc::SharedMemory.create`/`.open` over pagefile-backed
|
|
15
|
-
named file mappings, with bounds-checked `#read`/`#write`/`#flush`.
|
|
16
|
-
- **Named synchronization** — `Winipc::Mutex`, `Winipc::Event`, and
|
|
17
|
-
`Winipc::Semaphore` with `create`/`open`, GVL-releasing interruptible waits,
|
|
18
|
-
and auto-releasing `#synchronize` block forms. Abandoned mutexes are surfaced.
|
|
19
|
-
- **Secure by default** — pipe servers restrict access to the current user,
|
|
20
|
-
reject remote clients, and use `FILE_FLAG_FIRST_PIPE_INSTANCE`; handles are
|
|
21
|
-
non-inheritable; objects default to the per-session `Local\` namespace. An
|
|
22
|
-
explicit SDDL string or `:everyone` is opt-in.
|
|
23
|
-
- Error taxonomy under `Winipc::Error` (`OSError` with `#code`, plus
|
|
24
|
-
`TimeoutError`, `NotFound`, `Exists`, `AccessDenied`, `BrokenPipe`, `PipeBusy`,
|
|
25
|
-
`Canceled`, `NotOwner`, `WouldExceedMax`, `ModeError`, `RangeError`, `Closed`,
|
|
26
|
-
`Abandoned`).
|
|
27
|
-
|
|
28
|
-
Pure C extension over kernel32 + advapi32 (`rb_raise`/longjmp-safe; every handle
|
|
29
|
-
freed by a TypedData finalizer). Windows MSVC (mswin) Ruby only.
|