winloop 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6920000bbfa8ee7b4ddb3a773e560ec9fd17ebf7912926fd47ea838b9ccf1ceb
4
- data.tar.gz: 9c935d93743bf1858e5ab64326dbd3a2b32230a98672f4fbd76417095ceb2e1b
3
+ metadata.gz: 421971bb70dfb847bc33075448fd61cb6b5821f8c8a4d2db1b9af90f40ecc151
4
+ data.tar.gz: 93cb1c5234f1c337fb9978725672b8607490bf904302e06e3044b5171882df63
5
5
  SHA512:
6
- metadata.gz: 3313cb9155d745caa65538b904b8615b6fd3f700c8d9d1d14028e0042fd3de2581acabfd126d783d22989c01d1cee28560f7231209ae6680f94561087e5e30dc
7
- data.tar.gz: 6a7dda120d347e68e1dcd433eea90db639a7b9dcbfc15d47220b0503ee0ae8f29fc15b1e4a29cc3b8b79dda096e4ebc3607819e798b8339a9aefb15137f54ec3
6
+ metadata.gz: c9fc6ac85100e03148872e24cef8beadd10e4989d34f748fb30c94d0b0f08b55a672483eb60f0e5edce7fca605acbfb12053c7bd6d68a7d135f0c8a60ed1eb92
7
+ data.tar.gz: c8b7b095198daecccc195c3b69eb4c67812f653911a552301bc191e4e010ab57f4f88aaa6ffe7a751af50ce1e13b056779a8a6dc88085566a2fc15aa8de7b199
data/README.md CHANGED
@@ -1,278 +1,11 @@
1
- # winloop
2
-
3
- A native **Windows** Fiber Scheduler for MRI Ruby, built on **I/O Completion Ports**.
4
-
5
- `winloop` makes ordinary blocking code — socket reads and writes, `sleep`,
6
- `Timeout.timeout`, `Mutex`, `Queue`, `Thread#join`, DNS lookups — run
7
- cooperatively on a single thread. Wrap your code in `Winloop.run { ... }`,
8
- spawn fibers with `Fiber.schedule`, and thousands of connections share one
9
- thread without you touching a single nonblocking API.
10
-
11
- ```ruby
12
- require "winloop"
13
- require "socket"
14
-
15
- Winloop.run do
16
- server = TCPServer.new("127.0.0.1", 9292)
17
- loop do
18
- client = server.accept # readiness via AFD poll on the IOCP
19
- Fiber.schedule do # one lightweight fiber per connection
20
- while (line = client.gets) # recv driven by the completion port
21
- client.write(line) # send, never blocking the loop
22
- end
23
- client.close
24
- end
25
- end
26
- end
27
- ```
28
-
29
- ## Why this exists
30
-
31
- Ruby's `Fiber::Scheduler` (the engine behind `socketry/async` and friends) is the
32
- modern way to write concurrent I/O. But on Windows the selectors that back it —
33
- `socketry/io-event` ships `select`, `epoll`, `kqueue`, and `io_uring` — have **no
34
- IOCP backend**. The result has long been the weakest async story of any major
35
- platform: `select()`-bound, capped at 64 handles, and slow.
36
-
37
- `winloop` fills that hole the same way libuv, Rust's mio, and wepoll do it:
38
-
39
- * The event loop is a real **I/O Completion Port**. One
40
- `GetQueuedCompletionStatusEx` call per turn reaps every ready event at once.
41
- * **Readiness** (accept, connect, `IO#wait_readable`) — which IOCP doesn't natively
42
- provide — is obtained by submitting `IOCTL_AFD_POLL` against `\Device\Afd` as an
43
- overlapped operation on the port. This is the undocumented mechanism libuv/mio
44
- rely on, and it is the only way to get readiness semantics onto a completion port.
45
- * **Reads and writes** are driven by `recv`/`send` once the port reports readiness.
46
- * **Timers** (`sleep`, `Timeout.timeout`) use a monotonic min-heap that sets the
47
- port's wait timeout — no kernel timer objects, no extra threads.
48
- * **`Mutex`/`Queue`/`Thread#join`** park the fiber on an in-process waiter list; a
49
- wakeup from another OS thread breaks the loop's wait via
50
- `PostQueuedCompletionStatus`.
51
-
52
- ## Requirements
53
-
54
- * A **native Windows MSVC build of Ruby** (`x64-mswin64`). winloop compiles a C
55
- extension with `cl.exe` and links `ws2_32`. It will refuse to install on any
56
- non-`mswin` platform.
57
- * Ruby >= 3.1 (for a stable `Fiber::Scheduler` and `IO::Buffer`).
58
- * `\Device\Afd` readiness polling is a Windows NT facility; winloop is **not**
59
- expected to work under Wine.
60
-
61
- ## Installation
62
-
63
- ```sh
64
- gem install winloop
65
- ```
66
-
67
- Or build from a checkout (it dogfoods the [`vcvars`](https://rubygems.org/gems/vcvars)
68
- gem so it can find the MSVC toolchain without a Developer Command Prompt):
69
-
70
- ```sh
71
- rake compile
72
- rake test
73
- ```
74
-
75
- ## What runs cooperatively
76
-
77
- Inside `Winloop.run { ... }` (and any `Fiber.schedule` started within it):
78
-
79
- | Operation | Hook | Mechanism |
80
- | ------------------------------------------ | --------------- | -------------------------------------- |
81
- | `TCPServer#accept`, `IO#wait_readable` | `io_wait` | `IOCTL_AFD_POLL` overlapped on the IOCP |
82
- | `IO#read` / `gets` / `recv` on a socket | `io_read` | `recv_nonblock` + `io_wait` |
83
- | `IO#write` / `puts` on a socket | `io_write` | `write_nonblock` + `io_wait` |
84
- | `sleep` | `kernel_sleep` | monotonic timer heap |
85
- | `Timeout.timeout` | `timeout_after` | timer heap + `Fiber#raise` |
86
- | `Mutex`, `ConditionVariable`, `Queue`, `Thread#join` | `block`/`unblock` | waiter lists + `PostQueuedCompletionStatus` |
87
- | `Addrinfo.getaddrinfo`, DNS in `TCPSocket.new` | `address_resolve` | resolved on a worker thread |
88
- | `Process.wait` | `process_wait` | reaped on a worker thread |
89
- | your own OVERLAPPED ops (any gem) | `await_op` | generic completion packets on the same IOCP |
90
-
91
- ## API
92
-
93
- ```ruby
94
- Winloop.run { ... } # install a scheduler, run the block in a fiber, drive the
95
- # loop until every scheduled fiber finishes; returns the
96
- # block's value (and re-raises anything it raised).
97
-
98
- Winloop.supported? # true when the native IOCP/AFD backend loaded.
99
- ```
100
-
101
- For full control you can drive it yourself, exactly like any other
102
- `Fiber::Scheduler`:
103
-
104
- ```ruby
105
- scheduler = Winloop::Scheduler.new
106
- Fiber.set_scheduler(scheduler)
107
- Fiber.schedule { ... }
108
- scheduler.close # runs the event loop to completion
109
- ```
110
-
111
- The generic-op surface (winloop 0.2 — see the next section):
112
-
113
- ```ruby
114
- # Scheduler (the cross-gem protocol; loop-thread-only):
115
- sched.op_associate(handle) # => true (permanent; handle stays yours)
116
- sched.op_prepare(handle, tag: 0, capacity: 0) # => [op_id, ov_addr, buf_addr]
117
- sched.op_submitted(op_id) # => true (native call returned TRUE or ERROR_IO_PENDING)
118
- sched.op_abandon(op_id) # => true (native call failed synchronously)
119
- sched.op_cancel(op_id) # => true | false (false: already completed, or CancelIoEx failed — warned, never raised)
120
- sched.op_state(op_id) # => :prepared | :submitted | :completed
121
- sched.await_op(op_id, timeout: nil) # => [bytes, error, data] | nil (timeout; op auto-cancelled)
122
-
123
- # Backend (the power layer; also the standalone-embedding layer):
124
- backend.associate(handle) # => true
125
- backend.op_prepare(handle, tag: 0, capacity: 0) # => [op_id, ov_addr, buf_addr]
126
- backend.op_submitted(op_id) # => true
127
- backend.op_abandon(op_id) # => true
128
- backend.op_cancel(op_id) # => true | false
129
- backend.op_result(op_id) # => [bytes, error, data] (frees the record)
130
- backend.op_free(op_id) # => true (retire a completion nobody wants)
131
- backend.op_state(op_id) # => :prepared | :submitted | :completed
132
- backend.port_handle # => Integer (read-only; tests/diagnostics)
133
- backend.wait(timeout_ms) # => [[id, events], ..., [op_id, bytes, error, tag], ...]
134
- Winloop::EXTERNAL_KEY # => 0x45585431 — completion key of all generic ops
135
- Winloop::OP_CAPACITY_MAX # => 16 MiB — op_prepare capacity ceiling
136
- ```
137
-
138
- ## Bring your own OVERLAPPED (generic completions)
139
-
140
- **winloop 0.2 lets any gem associate a Windows handle with the loop's completion
141
- port, submit its own overlapped operation, and park the calling fiber until the
142
- completion packet arrives — cancel and synchronous-completion semantics done
143
- right, with zero change to everything winloop already does.** This is a seam for
144
- **gem authors** (file watchers, named pipes, mailslots…), not app code.
145
-
146
- The contract loop: `op_associate` → `op_prepare` → your native submit →
147
- `op_submitted` (success **or** `ERROR_IO_PENDING` — both queue a packet) /
148
- `op_abandon` (synchronous failure — the only no-packet case) → `await_op` →
149
- `[bytes, error, data]`.
150
-
151
- A real overlapped file read driven from plain Ruby via Fiddle (stdlib):
152
-
153
- ```ruby
154
- require "winloop"
155
- require "fiddle/import"
156
-
157
- module K32
158
- extend Fiddle::Importer
159
- dlload "kernel32.dll"
160
- extern "void* CreateFileW(void*, unsigned long, unsigned long, void*, unsigned long, unsigned long, void*)"
161
- extern "int ReadFile(void*, void*, unsigned long, void*, void*)"
162
- extern "int CloseHandle(void*)"
163
- end
164
-
165
- GENERIC_READ = 0x8000_0000
166
- FILE_SHARE_READ = 0x1
167
- OPEN_EXISTING = 3
168
- FILE_FLAG_OVERLAPPED = 0x4000_0000
169
- ERROR_IO_PENDING = 997
170
-
171
- Winloop.run do
172
- sched = Fiber.scheduler # a Winloop::Scheduler
173
- wpath = (File.expand_path(__FILE__) + "\0").encode("UTF-16LE")
174
- h = K32.CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, nil,
175
- OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nil).to_i
176
- sched.op_associate(h)
177
- op_id, ov, buf = sched.op_prepare(h, capacity: 4096)
178
- ok = K32.ReadFile(h, buf, 4096, nil, ov)
179
- if ok != 0 || Fiddle.win32_last_error == ERROR_IO_PENDING
180
- sched.op_submitted(op_id) # success AND pending both queue a packet
181
- bytes, error, data = sched.await_op(op_id, timeout: 5) # fiber parks HERE
182
- raise Winloop::Error, "read failed (#{error})" unless error.zero?
183
- puts "read #{bytes} bytes: #{data[0, 40].inspect}"
184
- else
185
- sched.op_abandon(op_id) # synchronous failure: no packet will come
186
- raise Winloop::Error, "ReadFile failed (#{Fiddle.win32_last_error})"
187
- end
188
- K32.CloseHandle(h)
189
- end
190
- ```
191
-
192
- ### Rules (read this twice)
193
-
194
- * **Handles stay yours.** winloop owns the op memory (OVERLAPPED + buffer, one
195
- backend-owned heap allocation); you open and close your handles — winloop
196
- never calls `CloseHandle` on them.
197
- * **Association is permanent** — one port per handle, no disassociation (Win32
198
- rule). After the loop shuts down an associated handle can never join another
199
- winloop loop; re-open per `Winloop.run` session.
200
- * **Associate before prepare.** `op_prepare` rejects handles it has never seen,
201
- because an op on a handle that is not on the port never completes: not an
202
- error, a **silent hang**. The one case the check cannot catch — closing a
203
- handle mid-flight so the OS recycles its value — is on you: cancel → await →
204
- only then `CloseHandle`.
205
- * **Use `await_op` timeouts while first integrating a new native API**; switch
206
- to `nil` once the submit path is proven.
207
- * Never set `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` on an associated handle (it
208
- is irreversible and per-handle): winloop's invariant is exactly one packet per
209
- submitted op, always. Don't use `ReadFileEx`/`WriteFileEx` (APC I/O) on
210
- associated handles, and don't duplicate/inherit them.
211
- * **Cancel never frees** — the cancelled op still posts a packet (usually error
212
- 995), which is reaped normally.
213
- * **After `await_op` returns — value or `nil` — the op id is dead.** Don't use
214
- it again.
215
- * Loop thread only: ops are submitted on the loop thread (fibers run there
216
- anyway); the scheduler wrappers enforce it.
217
- * **Completion errors are values, not exceptions**: `995` cancelled, `1022`
218
- RDCW rescan, `38` EOF, … A zero-byte successful completion is a real signal
219
- (RDCW overflow ⇒ rescan), delivered verbatim.
220
- * No PQCS injection — out of scope in v1. `Backend#port_handle` exists for
221
- tests and diagnostics only.
222
-
223
- ### Standalone (no scheduler)
224
-
225
- Client gems never hard-depend on winloop. The protocol is duck-typed on the
226
- scheduler:
227
-
228
- ```ruby
229
- sched = Fiber.scheduler
230
- if sched&.respond_to?(:await_op)
231
- # winloop (or compatible) IOCP path: op_associate / op_prepare / native
232
- # submit / op_submitted / await_op — as above.
233
- else
234
- # the client gem's own standalone mechanism, e.g. the winipc pattern:
235
- # stack OVERLAPPED + manual-reset hEvent, WaitForSingleObject without the
236
- # GVL with ubf = CancelIoEx, GetOverlappedResult; or a gem-owned wait thread
237
- # for continuous streams.
238
- end
239
- ```
240
-
241
- winloop ships **no** shared standalone wait thread or service (dependency
242
- direction, irreversible association, ownership — clients own their threads).
243
- `Winloop::Backend` itself does work standalone — create, `associate`,
244
- prepare/submit, `wait`-poll (dispatch on tuple size: AFD 2-tuples vs op
245
- 4-tuples in the same array), `op_result`, `shutdown` — which is how an embedder
246
- or a test drives it deterministically.
247
-
248
- ## Scope and limitations (v0.2)
249
-
250
- * **Sockets are the focus.** TCP/UDP sockets get the full IOCP/AFD path. Pipes and
251
- regular files fall back to a blocking read inside `io_read` (async file I/O on
252
- Windows is a separate mechanism, planned for later).
253
- * **Single thread-of-use.** One thread runs the loop and resumes fibers; other OS
254
- threads may safely wake it (e.g. a background `Queue` producer), but the loop
255
- itself is not a multi-threaded worker pool.
256
- * `IOCTL_AFD_POLL` is undocumented; winloop resolves its `ntdll` entry points at
257
- runtime and degrades by raising a clear error if `\Device\Afd` is unavailable.
258
- * **The ops API is for gem authors.** Buffers are backend-owned only (no
259
- IO::Buffer/String pinning — copy-out removes the whole lifetime/compaction bug
260
- class). Ops still in flight at shutdown are cancelled and drained for a
261
- bounded interval; stragglers may be deliberately leaked rather than freed
262
- under possible kernel ownership.
263
- * **x64 only.** arm64-mswin is expected to work (all code is `_WIN64`/
264
- `uintptr_t`-clean, no arch-specific anything — AFD + IOCP are
265
- production-proven on arm64 via libuv/Node and mio/Rust) but is untested and
266
- unsupported until an arm64-mswin Ruby distribution exists.
267
-
268
- ## How it compares
269
-
270
- `winloop` is a drop-in `Fiber::Scheduler`, so it works with anything that targets
271
- the scheduler interface. It is *not* a reimplementation of `async` — it's the
272
- missing Windows engine such libraries can run on. The reference scheduler shipped
273
- with Ruby is intended for testing and is `IO.select`-bound; winloop replaces that
274
- selector with a true completion port.
275
-
276
- ## License
277
-
278
- MIT. See [LICENSE.txt](LICENSE.txt).
1
+ # winloop (discontinued)
2
+
3
+ **winloop is no longer maintained.** This final release (0.2.1) contains no
4
+ code: installing it compiles nothing, and `require "winloop"` raises a
5
+ `LoadError` explaining the discontinuation.
6
+
7
+ Use async / io-event (native Windows IOCP support is in progress upstream) 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/winloop.rb CHANGED
@@ -1,81 +1,4 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "winloop/version"
4
-
5
- # winloop — a native Windows (IOCP) Fiber Scheduler for MRI Ruby.
6
- #
7
- # require "winloop"
8
- #
9
- # Winloop.run do
10
- # server = TCPServer.new("127.0.0.1", 9292)
11
- # loop do
12
- # client = server.accept # io_wait via AFD poll
13
- # Fiber.schedule do # one fiber per connection
14
- # while (line = client.gets) # io_read via recv_nonblock + io_wait
15
- # client.write(line)
16
- # end
17
- # client.close
18
- # end
19
- # end
20
- # end
21
- #
22
- # All blocking socket I/O, sleeps, timeouts and Mutex/Queue/Thread#join inside
23
- # the block run cooperatively on a single thread, driven by one I/O Completion
24
- # Port. See Winloop::Scheduler for the hook implementations.
25
- module Winloop
26
- class Error < StandardError; end unless const_defined?(:Error)
27
-
28
- # Loaded for its side effect of defining Winloop::Backend + the IO event
29
- # constants (READABLE/WRITABLE/PRIORITY). mswin-only.
30
- begin
31
- require "winloop/winloop"
32
- rescue LoadError => e
33
- raise LoadError, "winloop's native backend failed to load — winloop needs a " \
34
- "Windows MSVC (mswin) Ruby. (#{e.message})"
35
- end
36
-
37
- require_relative "winloop/scheduler"
38
- require_relative "winloop/ops" # generic-op validation layer (OP_CAPACITY_MAX, op_prepare)
39
-
40
- module_function
41
-
42
- # True if the IOCP/AFD backend is available on this platform.
43
- def supported?
44
- const_defined?(:Backend)
45
- end
46
-
47
- # Install a fresh Winloop::Scheduler on the current thread, run `block` inside
48
- # a non-blocking fiber, drive the event loop until every scheduled fiber has
49
- # finished, and return the block's value.
50
- #
51
- # Nesting is not supported; if a scheduler is already installed, the block is
52
- # simply scheduled on it.
53
- def run
54
- raise ArgumentError, "Winloop.run requires a block" unless block_given?
55
-
56
- # Already inside a scheduler (e.g. a nested Winloop.run within a fiber): just
57
- # run the block here — blocking operations already cooperate with that loop.
58
- return yield if Fiber.scheduler
59
-
60
- scheduler = Scheduler.new
61
- Fiber.set_scheduler(scheduler)
62
- result = nil
63
- error = nil
64
- Fiber.schedule do
65
- begin
66
- result = yield
67
- rescue Exception => e # rubocop:disable Lint/RescueException
68
- error = e
69
- end
70
- end
71
- scheduler.close # drives the loop to completion
72
- raise error if error
73
- result
74
- ensure
75
- # Detach our (now-closed) scheduler so subsequent ordinary I/O on this
76
- # thread doesn't hit a closed backend. Safe here: we are on the main fiber
77
- # and close has already returned (the reentrant-close trap only bites when
78
- # set_scheduler(nil) is called from *inside* #close during finalization).
79
- Fiber.set_scheduler(nil) if Fiber.scheduler.equal?(scheduler)
80
- end
81
- end
1
+ # frozen_string_literal: true
2
+
3
+ raise LoadError, "winloop is discontinued and this release contains no code. Use async / io-event (native Windows IOCP support is in progress upstream) instead." \
4
+ " Earlier winloop releases are unmaintained and not recommended."
metadata CHANGED
@@ -1,87 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winloop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.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
- - !ruby/object:Gem::Dependency
13
- name: rake
14
- requirement: !ruby/object:Gem::Requirement
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
- description: |
55
- winloop is a Ruby Fiber::Scheduler built on Win32 I/O Completion Ports. It
56
- makes ordinary socket I/O, sleeps, timeouts and Mutex/Queue/Thread#join run
57
- cooperatively on a single thread — the async-runtime story that has always
58
- been weak on Windows, done the way libuv/mio/wepoll do it: readiness over an
59
- IOCP via \Device\Afd polling, with recv/send driven by the completion port.
60
- Requires a native Windows MSVC (mswin) build of Ruby.
11
+ dependencies: []
12
+ description: winloop is discontinued. This final release contains no code and exists
13
+ only so that installing winloop explains the discontinuation instead of building
14
+ an unmaintained native extension.
61
15
  executables: []
62
- extensions:
63
- - ext/winloop/extconf.rb
16
+ extensions: []
64
17
  extra_rdoc_files: []
65
18
  files:
66
- - CHANGELOG.md
67
19
  - LICENSE.txt
68
20
  - README.md
69
- - ext/winloop/extconf.rb
70
- - ext/winloop/winloop.c
71
21
  - lib/winloop.rb
72
- - lib/winloop/ops.rb
73
- - lib/winloop/scheduler.rb
74
- - lib/winloop/version.rb
75
- homepage: https://github.com/main-path/winloop
76
22
  licenses:
77
23
  - MIT
78
24
  metadata:
79
- homepage_uri: https://github.com/main-path/winloop
80
- source_code_uri: https://github.com/main-path/winloop
81
- changelog_uri: https://github.com/main-path/winloop/blob/main/CHANGELOG.md
82
- bug_tracker_uri: https://github.com/main-path/winloop/issues
83
- allowed_push_host: https://rubygems.org
84
25
  rubygems_mfa_required: 'true'
26
+ post_install_message: winloop is discontinued and this release contains no code. Use
27
+ async / io-event (native Windows IOCP support is in progress upstream) instead.
85
28
  rdoc_options: []
86
29
  require_paths:
87
30
  - lib
@@ -89,7 +32,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
32
  requirements:
90
33
  - - ">="
91
34
  - !ruby/object:Gem::Version
92
- version: '3.1'
35
+ version: '2.0'
93
36
  required_rubygems_version: !ruby/object:Gem::Requirement
94
37
  requirements:
95
38
  - - ">="
@@ -98,5 +41,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
41
  requirements: []
99
42
  rubygems_version: 3.6.9
100
43
  specification_version: 4
101
- summary: A native Windows (IOCP) Fiber Scheduler for MRI Ruby.
44
+ summary: Discontinued. Use async / io-event (native Windows IOCP support is in progress
45
+ upstream) instead.
102
46
  test_files: []
data/CHANGELOG.md DELETED
@@ -1,88 +0,0 @@
1
- # Changelog
2
-
3
- ## 0.2.0
4
-
5
- **Bring your own OVERLAPPED** — a generic OVERLAPPED/IOCP completion API, so any
6
- gem can associate a Windows handle with the loop's completion port, submit its
7
- own overlapped operation, and park the calling fiber until the completion packet
8
- arrives. Strictly additive: nothing in the 0.1 API changes signature or behavior,
9
- and all 34 pre-existing tests pass byte-unmodified.
10
-
11
- ### Added
12
-
13
- * **`Winloop::Backend` op API** (the power layer; also works standalone, with no
14
- scheduler): `#associate(handle)`, `#op_prepare(handle, tag:, capacity:)` →
15
- `[op_id, ov_addr, buf_addr]` (backend-owned OVERLAPPED + 8-byte-aligned
16
- embedded buffer), `#op_submitted`, `#op_abandon` (synchronous-failure path —
17
- the only no-packet case), `#op_cancel` (`CancelIoEx`; **never frees** — the
18
- cancelled op still posts a packet, which is reaped normally; CancelIoEx
19
- failures warn and return `false`, never raise), `#op_result` →
20
- `[bytes, error, data]`, `#op_free`, `#op_state`, and `#port_handle`
21
- (tests/diagnostics only).
22
- * **`Winloop::Scheduler` op protocol** (the misuse-resistant cross-gem surface,
23
- feature-gated by `Fiber.scheduler.respond_to?(:await_op)`): `op_associate`,
24
- `op_prepare`, `op_submitted`, `op_abandon`, `op_cancel`, `op_state`, and
25
- `await_op(op_id, timeout: nil)`, which parks the calling fiber until the
26
- completion is reaped (returns the `[bytes, error, data]` triple, or `nil` on
27
- timeout after auto-cancelling and orphaning the op). Loop-thread-only,
28
- enforced.
29
- * Constants `Winloop::EXTERNAL_KEY` (the completion key carried by all generic
30
- ops) and `Winloop::OP_CAPACITY_MAX` (16 MiB embedded-buffer ceiling).
31
- * **Completion errors are data, not exceptions**: the `error` element carries
32
- the Win32 code mapped from the final NTSTATUS via `RtlNtStatusToDosError`
33
- (`0` success, `995` cancelled, `1022` RDCW rescan, `38` EOF, …).
34
- * `op_prepare` rejects handles never passed to `#associate` — an op on a handle
35
- that is not on the port never completes (a silent hang, not an error).
36
- * Defensive reap: unknown EXT packets and duplicate posts for already-completed
37
- ops are skipped with a warning, never cast, never re-completed.
38
- * `ObjectSpace.memsize_of(backend)` now reports live op-buffer bytes.
39
- * Gemspec metadata tightened to suite grade: `bug_tracker_uri`,
40
- `rubygems_mfa_required`.
41
-
42
- ### Changed
43
-
44
- * `Backend#shutdown` (and the GC free hook) now also cancels every in-flight
45
- generic op per op (`CancelIoEx(handle, ov)`), drains their packets — including
46
- one bounded 100 ms wait taken **only** when generic ops are still in flight —
47
- and frees retired records. Ops whose packet never surfaced inside the bounded
48
- drain are deliberately leaked (the kernel may still write their
49
- OVERLAPPED/IOSB; a use-after-free beats a bounded leak). Existing AFD-only
50
- workloads see byte-identical shutdown.
51
- * `Backend#wait` may now return 4-element arrays `[op_id, bytes, error, tag]`
52
- alongside the existing AFD 2-tuples — when, and only when, the new op API is
53
- used in the process. Callers dispatch on tuple size; the AFD contract
54
- (2-tuples, `[]` on timeout, wakeups skipped) is untouched.
55
-
56
- ## 0.1.0
57
-
58
- Initial release.
59
-
60
- * A `Fiber::Scheduler` for MRI on Windows backed by I/O Completion Ports.
61
- * C backend (`Winloop::Backend`): one IOCP plus a `\Device\Afd` handle, with
62
- one-shot readiness polls (`IOCTL_AFD_POLL`) submitted as overlapped operations
63
- on the port, reaped in batches via `GetQueuedCompletionStatusEx`. The wait runs
64
- without the GVL so background threads can wake the loop with
65
- `PostQueuedCompletionStatus`.
66
- * Scheduler hooks: `io_wait`, `io_read`, `io_write`, `kernel_sleep`,
67
- `timeout_after`, `block`/`unblock`, `fiber`, `address_resolve`, `process_wait`,
68
- and `close` (which drives the event loop).
69
- * Monotonic timer min-heap for `sleep` and `Timeout.timeout`.
70
- * `Winloop.run { ... }` convenience entry point.
71
- * Verified with TCP echo over many concurrent connections, large multi-chunk
72
- transfers, EOF handling, `Timeout` (both firing and not), cross-thread `Queue`,
73
- inter-fiber `Mutex` contention, `Thread#join`, DNS resolution, and `IO.popen`.
74
-
75
- Hardened over three rounds of adversarial review (the GVL is released around the
76
- completion wait so background threads can wake the loop):
77
- * AFD readiness polls are **coalesced per socket** — multiple fibers waiting the
78
- same socket for the same event all wake (the AFD driver only completes one poll
79
- per readiness edge, so independent polls would lose wakeups).
80
- * Cross-thread wakeups are unified through a single per-fiber guard, so a timeout
81
- timer racing an `unblock` (e.g. `ConditionVariable#wait(mutex, timeout)` racing a
82
- signal, or `Thread#join(timeout)` near its deadline) can never double-resume a
83
- fiber and cut its next wait short.
84
- * Settled timers are dropped from the heap so an early-resolved wait can't pin the
85
- loop asleep until its original (possibly long) timeout.
86
- * An unhandled exception in a scheduled fiber is reported, not fatal to the loop.
87
- * The non-socket read fallback runs on a worker thread that is killed if its fiber
88
- unwinds, so it can't leak or steal data; pipe EOF is silent.
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
- #
3
- # extconf.rb for the winloop C backend (IOCP + \Device\Afd readiness).
4
-
5
- require "mkmf"
6
-
7
- unless RbConfig::CONFIG["target_os"] =~ /mswin/
8
- abort <<~MSG
9
- winloop requires a native Windows MSVC (mswin) Ruby — its event loop is built
10
- on Win32 I/O Completion Ports and \\Device\\Afd readiness polling (cl.exe).
11
- Your Ruby is "#{RbConfig::CONFIG['arch']}".
12
- MSG
13
- end
14
-
15
- # Winsock. (ntdll's Nt*/Rtl* entry points are resolved at runtime via
16
- # GetProcAddress, so no ntdll import lib is required.) Pure C — no -EHsc.
17
- $libs = [$libs, "ws2_32.lib"].join(" ")
18
-
19
- create_makefile("winloop/winloop")