winipc 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 628224ea1bed3bec0541f134c74f786132ffc060750d07af59e1f97f0273d484
4
+ data.tar.gz: af11bf3b0527555f5cc1df9542dee9b8e3c8249d52fbf3999e1b461028d052f5
5
+ SHA512:
6
+ metadata.gz: a8c03faa73dcddedd6727f5784d311c361ebadd4c93efe16ef1519dbae81e6049bbccc5bdf580606338c55dc7d806dbe1ae026146e7a777547f100329b8da497
7
+ data.tar.gz: 7ec5eb17dc9d44200e40676b0425865f11a79e5e5faf11d33550e0ca8d2a1d3825bbf40773669640e838c11cda362158193b98c7ba8a684e82f7fa4296b72dea
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
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.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ned
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # winipc
2
+
3
+ **Windows local IPC for Ruby: named pipes, shared memory, and named synchronization — safe by default, and cooperative under a fiber scheduler.**
4
+
5
+ `winipc` is a native extension that exposes the three pillars of Windows
6
+ inter-process communication through an ergonomic, hard-to-misuse Ruby API:
7
+
8
+ - **Named pipes** — a duplex server and a connect-with-retry client, byte or
9
+ message framed. Opened for overlapped I/O, so blocking calls release the GVL
10
+ and (under a fiber scheduler such as [winloop](https://rubygems.org/gems/winloop))
11
+ run **cooperatively** instead of stalling the loop.
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).
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # extconf.rb for the winipc extension — Windows local IPC (named pipes,
4
+ # shared memory / file mappings, and named synchronization objects).
5
+
6
+ require "mkmf"
7
+
8
+ unless RbConfig::CONFIG["target_os"] =~ /mswin/
9
+ abort <<~MSG
10
+ winipc requires a native Windows MSVC (mswin) Ruby — it binds Win32 named
11
+ pipes, file-mapping shared memory, and named synchronization objects, and is
12
+ built with cl.exe. Your Ruby is "#{RbConfig::CONFIG['arch']}".
13
+ MSG
14
+ end
15
+
16
+ # kernel32 (pipes / file mappings / sync objects) is linked by default.
17
+ # advapi32 provides ConvertStringSecurityDescriptorToSecurityDescriptorW for the
18
+ # safe-by-default security descriptors. Pure C — no -EHsc.
19
+ $libs = [$libs, "advapi32.lib"].join(" ")
20
+
21
+ create_makefile("winipc/winipc")