shuttlebay 0.1.4

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: 4038d15bb64a686fa2abc8662520807bbdedc3ac6d3796688ccb57c4acef8aaa
4
+ data.tar.gz: 1bfb6f3eb2456f5b3b16117a45d5daa97f9c58e08420bf53c888d023b3f28210
5
+ SHA512:
6
+ metadata.gz: 472a1f07635596621b47dfd053f6ffacb4d579def811317a90160cff2d9c52c547ee562f6b2071f2e559fe1228e9a18ecccb3fcf8457d2cb3fcdc6707f67bf1b
7
+ data.tar.gz: ffba9bc614e3b26f51db20e397d44cb640ca32a6ca58bda9e7c33f26eb4d9627991ee99e981cab37f9c047d2162980505f3aa1448695e229fd80a2f3b7ba3d37
data/Cargo.lock ADDED
@@ -0,0 +1,416 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "aho-corasick"
7
+ version = "1.1.5"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
10
+ dependencies = [
11
+ "memchr",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "bindgen"
16
+ version = "0.72.1"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
19
+ dependencies = [
20
+ "bitflags",
21
+ "cexpr",
22
+ "clang-sys",
23
+ "itertools",
24
+ "proc-macro2",
25
+ "quote",
26
+ "regex",
27
+ "rustc-hash",
28
+ "shlex",
29
+ "syn 2.0.119",
30
+ ]
31
+
32
+ [[package]]
33
+ name = "bitflags"
34
+ version = "2.13.2"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
37
+
38
+ [[package]]
39
+ name = "bytes"
40
+ version = "1.12.1"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
43
+
44
+ [[package]]
45
+ name = "cexpr"
46
+ version = "0.6.0"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
49
+ dependencies = [
50
+ "nom",
51
+ ]
52
+
53
+ [[package]]
54
+ name = "cfg-if"
55
+ version = "1.0.5"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
58
+
59
+ [[package]]
60
+ name = "clang-sys"
61
+ version = "1.9.1"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
64
+ dependencies = [
65
+ "glob",
66
+ "libc",
67
+ "libloading",
68
+ ]
69
+
70
+ [[package]]
71
+ name = "either"
72
+ version = "1.18.0"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
75
+
76
+ [[package]]
77
+ name = "glob"
78
+ version = "0.3.4"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
81
+
82
+ [[package]]
83
+ name = "itertools"
84
+ version = "0.13.0"
85
+ source = "registry+https://github.com/rust-lang/crates.io-index"
86
+ checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
87
+ dependencies = [
88
+ "either",
89
+ ]
90
+
91
+ [[package]]
92
+ name = "itoa"
93
+ version = "1.0.18"
94
+ source = "registry+https://github.com/rust-lang/crates.io-index"
95
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
96
+
97
+ [[package]]
98
+ name = "lazy_static"
99
+ version = "1.5.1"
100
+ source = "registry+https://github.com/rust-lang/crates.io-index"
101
+ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
102
+
103
+ [[package]]
104
+ name = "libc"
105
+ version = "0.2.190"
106
+ source = "registry+https://github.com/rust-lang/crates.io-index"
107
+ checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
108
+
109
+ [[package]]
110
+ name = "libloading"
111
+ version = "0.8.9"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
114
+ dependencies = [
115
+ "cfg-if",
116
+ "windows-link",
117
+ ]
118
+
119
+ [[package]]
120
+ name = "magnus"
121
+ version = "0.9.2"
122
+ source = "registry+https://github.com/rust-lang/crates.io-index"
123
+ checksum = "b9068710e00d21be762edca59b9c4c8946dbdb29820d4f76ad559afea07982c9"
124
+ dependencies = [
125
+ "bytes",
126
+ "magnus-macros",
127
+ "rb-sys",
128
+ "rb-sys-env",
129
+ "seq-macro",
130
+ ]
131
+
132
+ [[package]]
133
+ name = "magnus-macros"
134
+ version = "0.9.1"
135
+ source = "registry+https://github.com/rust-lang/crates.io-index"
136
+ checksum = "9dafde2eeb66f090e0f326112323eb8324b12cb32b5d1210e541d3df6b0ce77e"
137
+ dependencies = [
138
+ "proc-macro2",
139
+ "quote",
140
+ "syn 2.0.119",
141
+ ]
142
+
143
+ [[package]]
144
+ name = "memchr"
145
+ version = "2.8.3"
146
+ source = "registry+https://github.com/rust-lang/crates.io-index"
147
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
148
+
149
+ [[package]]
150
+ name = "minimal-lexical"
151
+ version = "0.2.1"
152
+ source = "registry+https://github.com/rust-lang/crates.io-index"
153
+ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
154
+
155
+ [[package]]
156
+ name = "mothership-docking-protocol"
157
+ version = "0.2.0"
158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
159
+ checksum = "aaec5966e84e1a3c65b5846bd89c0c0621de6c75362f9c669636449fdf5a4b99"
160
+ dependencies = [
161
+ "serde",
162
+ "serde_json",
163
+ "thiserror",
164
+ ]
165
+
166
+ [[package]]
167
+ name = "nom"
168
+ version = "7.1.3"
169
+ source = "registry+https://github.com/rust-lang/crates.io-index"
170
+ checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
171
+ dependencies = [
172
+ "memchr",
173
+ "minimal-lexical",
174
+ ]
175
+
176
+ [[package]]
177
+ name = "proc-macro2"
178
+ version = "1.0.107"
179
+ source = "registry+https://github.com/rust-lang/crates.io-index"
180
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
181
+ dependencies = [
182
+ "unicode-ident",
183
+ ]
184
+
185
+ [[package]]
186
+ name = "quote"
187
+ version = "1.0.47"
188
+ source = "registry+https://github.com/rust-lang/crates.io-index"
189
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
190
+ dependencies = [
191
+ "proc-macro2",
192
+ ]
193
+
194
+ [[package]]
195
+ name = "rb-sys"
196
+ version = "0.9.130"
197
+ source = "registry+https://github.com/rust-lang/crates.io-index"
198
+ checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
199
+ dependencies = [
200
+ "rb-sys-build",
201
+ ]
202
+
203
+ [[package]]
204
+ name = "rb-sys-build"
205
+ version = "0.9.130"
206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
207
+ checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
208
+ dependencies = [
209
+ "bindgen",
210
+ "lazy_static",
211
+ "proc-macro2",
212
+ "quote",
213
+ "regex",
214
+ "shell-words",
215
+ "syn 2.0.119",
216
+ ]
217
+
218
+ [[package]]
219
+ name = "rb-sys-env"
220
+ version = "0.2.3"
221
+ source = "registry+https://github.com/rust-lang/crates.io-index"
222
+ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
223
+
224
+ [[package]]
225
+ name = "regex"
226
+ version = "1.13.1"
227
+ source = "registry+https://github.com/rust-lang/crates.io-index"
228
+ checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
229
+ dependencies = [
230
+ "aho-corasick",
231
+ "memchr",
232
+ "regex-automata",
233
+ "regex-syntax",
234
+ ]
235
+
236
+ [[package]]
237
+ name = "regex-automata"
238
+ version = "0.4.18"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
241
+ dependencies = [
242
+ "aho-corasick",
243
+ "memchr",
244
+ "regex-syntax",
245
+ ]
246
+
247
+ [[package]]
248
+ name = "regex-syntax"
249
+ version = "0.8.11"
250
+ source = "registry+https://github.com/rust-lang/crates.io-index"
251
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
252
+
253
+ [[package]]
254
+ name = "rustc-hash"
255
+ version = "2.1.3"
256
+ source = "registry+https://github.com/rust-lang/crates.io-index"
257
+ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
258
+
259
+ [[package]]
260
+ name = "seq-macro"
261
+ version = "0.3.6"
262
+ source = "registry+https://github.com/rust-lang/crates.io-index"
263
+ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
264
+
265
+ [[package]]
266
+ name = "serde"
267
+ version = "1.0.229"
268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
269
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
270
+ dependencies = [
271
+ "serde_core",
272
+ "serde_derive",
273
+ ]
274
+
275
+ [[package]]
276
+ name = "serde_core"
277
+ version = "1.0.229"
278
+ source = "registry+https://github.com/rust-lang/crates.io-index"
279
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
280
+ dependencies = [
281
+ "serde_derive",
282
+ ]
283
+
284
+ [[package]]
285
+ name = "serde_derive"
286
+ version = "1.0.229"
287
+ source = "registry+https://github.com/rust-lang/crates.io-index"
288
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
289
+ dependencies = [
290
+ "proc-macro2",
291
+ "quote",
292
+ "syn 3.0.6",
293
+ ]
294
+
295
+ [[package]]
296
+ name = "serde_json"
297
+ version = "1.0.151"
298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
299
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
300
+ dependencies = [
301
+ "itoa",
302
+ "memchr",
303
+ "serde",
304
+ "serde_core",
305
+ "zmij",
306
+ ]
307
+
308
+ [[package]]
309
+ name = "shell-words"
310
+ version = "1.1.1"
311
+ source = "registry+https://github.com/rust-lang/crates.io-index"
312
+ checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
313
+
314
+ [[package]]
315
+ name = "shlex"
316
+ version = "1.3.0"
317
+ source = "registry+https://github.com/rust-lang/crates.io-index"
318
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
319
+
320
+ [[package]]
321
+ name = "shuttlebay"
322
+ version = "0.1.0"
323
+ dependencies = [
324
+ "magnus",
325
+ "mothership-docking-protocol",
326
+ "rb-sys",
327
+ "serde_json",
328
+ "state-machines",
329
+ ]
330
+
331
+ [[package]]
332
+ name = "state-machines"
333
+ version = "0.30.0"
334
+ source = "registry+https://github.com/rust-lang/crates.io-index"
335
+ checksum = "f2bc4296143483a9608a37ba4cf9955b42b8e2154dc58917df4f9cbe8120ebb4"
336
+ dependencies = [
337
+ "state-machines-core",
338
+ "state-machines-macro",
339
+ ]
340
+
341
+ [[package]]
342
+ name = "state-machines-core"
343
+ version = "0.11.0"
344
+ source = "registry+https://github.com/rust-lang/crates.io-index"
345
+ checksum = "54b3da777f8a61919bdb3c6e9e445f2995f851b967e5d73b5949290dccfdcddd"
346
+
347
+ [[package]]
348
+ name = "state-machines-macro"
349
+ version = "0.30.0"
350
+ source = "registry+https://github.com/rust-lang/crates.io-index"
351
+ checksum = "edf80d0c48518c7fb7fbb1dd4dda13a8dc189c1f15ac435545b351d5f2c276b1"
352
+ dependencies = [
353
+ "proc-macro2",
354
+ "quote",
355
+ "syn 2.0.119",
356
+ ]
357
+
358
+ [[package]]
359
+ name = "syn"
360
+ version = "2.0.119"
361
+ source = "registry+https://github.com/rust-lang/crates.io-index"
362
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
363
+ dependencies = [
364
+ "proc-macro2",
365
+ "quote",
366
+ "unicode-ident",
367
+ ]
368
+
369
+ [[package]]
370
+ name = "syn"
371
+ version = "3.0.6"
372
+ source = "registry+https://github.com/rust-lang/crates.io-index"
373
+ checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
374
+ dependencies = [
375
+ "proc-macro2",
376
+ "quote",
377
+ "unicode-ident",
378
+ ]
379
+
380
+ [[package]]
381
+ name = "thiserror"
382
+ version = "2.0.21"
383
+ source = "registry+https://github.com/rust-lang/crates.io-index"
384
+ checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
385
+ dependencies = [
386
+ "thiserror-impl",
387
+ ]
388
+
389
+ [[package]]
390
+ name = "thiserror-impl"
391
+ version = "2.0.21"
392
+ source = "registry+https://github.com/rust-lang/crates.io-index"
393
+ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
394
+ dependencies = [
395
+ "proc-macro2",
396
+ "quote",
397
+ "syn 3.0.6",
398
+ ]
399
+
400
+ [[package]]
401
+ name = "unicode-ident"
402
+ version = "1.0.26"
403
+ source = "registry+https://github.com/rust-lang/crates.io-index"
404
+ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
405
+
406
+ [[package]]
407
+ name = "windows-link"
408
+ version = "0.2.1"
409
+ source = "registry+https://github.com/rust-lang/crates.io-index"
410
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
411
+
412
+ [[package]]
413
+ name = "zmij"
414
+ version = "1.0.23"
415
+ source = "registry+https://github.com/rust-lang/crates.io-index"
416
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
data/Cargo.toml ADDED
@@ -0,0 +1,15 @@
1
+ [workspace]
2
+ members = ["ext/shuttlebay"]
3
+ resolver = "3"
4
+
5
+ [workspace.package]
6
+ edition = "2024"
7
+ rust-version = "1.99"
8
+ license = "MIT"
9
+ repository = "https://github.com/seuros/shuttlebay"
10
+
11
+ [profile.release]
12
+ lto = true
13
+ codegen-units = 1
14
+ opt-level = 3
15
+ strip = true
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abdelkader Boudih
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,191 @@
1
+ # Shuttlebay
2
+
3
+ Run a Rack app as a [Mothership](https://github.com/seuros/mothership) `[[bays.http]]` bay.
4
+
5
+ Mothership is the HTTP server. Shuttlebay is the Ruby side: it preloads your
6
+ app, forks workers, and runs the app on threads that receive requests over
7
+ Unix-socket docking links. A native engine (Rust, via magnus) decodes the
8
+ docking protocol and builds the Rack env, so Ruby never parses HTTP.
9
+
10
+ ```mermaid
11
+ flowchart TD
12
+ client([client]) -- HTTP --> mothership["mothership<br/>static, gzip, routing, limits, request ids…"]
13
+ mothership -- "docking protocol v3<br/>over a Unix socket" --> master["shuttlebay master<br/>config.ru preloaded: Rails, Sinatra, Roda, Padrino, …"]
14
+ master --> w0["worker 0<br/>threads, one docking link each"]
15
+ master --> w1["worker 1<br/>threads"]
16
+ ```
17
+
18
+ ## Install
19
+
20
+ ```ruby
21
+ # Gemfile
22
+ gem "shuttlebay"
23
+ gem "guardship" # the mothership binary (or: cargo install mothership)
24
+ ```
25
+
26
+ ```toml
27
+ # ship-manifest.toml
28
+ [mothership.bind]
29
+ http = "0.0.0.0:3000"
30
+
31
+ [[mothership.static_dirs]] # public/ served by mothership, not Rails
32
+ path = "./public"
33
+ prefix = "/"
34
+
35
+ [[bays.http]]
36
+ name = "web"
37
+ command = "bundle"
38
+ args = ["exec", "shuttlebay", "config.ru"]
39
+ workers = "auto" # default: sized from CPUs and memory; 0 = single process
40
+ threads = 5
41
+ routes = [{ bind = "http", pattern = "/.*" }]
42
+ ```
43
+
44
+ ```bash
45
+ bundle exec mothership
46
+ ```
47
+
48
+ ## What Mothership does instead of Rack middleware
49
+
50
+ | Concern | Before (app server + middleware) | With Shuttlebay |
51
+ |---|---|---|
52
+ | HTTP parsing, keep-alive, slow clients | App server reactor | Mothership |
53
+ | Static files | `ActionDispatch::Static` / nginx | `[[mothership.static_dirs]]` |
54
+ | `send_file` / `Rack::Sendfile` | X-Sendfile + nginx | `to_path` bodies streamed from disk by Mothership |
55
+ | Compression | `Rack::Deflater` / nginx | `compression = true` |
56
+ | Request id, queue time | `ActionDispatch::RequestId`, LB | `X-Request-Id`, `X-Request-Start: t=<µs>` |
57
+ | Real client IP | `RemoteIp` + proxy config | `REMOTE_ADDR` resolved (Forwarded / PROXY protocol) |
58
+ | Load balancing to workers | kernel accept / `wait_for_less_busy_worker` | idle-thread checkout; queueing in Mothership |
59
+ | Request body buffering | App server | Mothership reads the whole body before claiming a thread |
60
+
61
+ When Rails is loaded, the bundled Railtie removes `Rack::Sendfile`, since the
62
+ engine offloads `to_path` bodies itself.
63
+
64
+ ## Hooks
65
+
66
+ `config.ru` loads in the master before forking, so register hooks there:
67
+
68
+ ```ruby
69
+ Shuttlebay.before_fork { ActiveRecord::Base.connection_pool.disconnect! }
70
+ Shuttlebay.on_worker_boot { |index| Rails.logger.info("worker #{index} up") }
71
+ Shuttlebay.on_worker_shutdown { |index| Rails.logger.info("worker #{index} drained") }
72
+ Shuttlebay.out_of_band { GC.start } # runs when a worker has no request in flight
73
+ run Rails.application
74
+ ```
75
+
76
+ ## Fibers
77
+
78
+ With a fiber scheduler, each worker runs its `threads` docking links as fibers
79
+ on one reactor thread instead of as threads. A request waiting on I/O parks
80
+ its fiber, and the reactor serves the other links meanwhile.
81
+
82
+ ```ruby
83
+ # config.ru (Gemfile: gem "async")
84
+ require "async"
85
+ Shuttlebay.fiber_scheduler { Async::Scheduler.new }
86
+ run Rails.application
87
+ ```
88
+
89
+ The block runs once per worker, after fork, on the reactor thread; any
90
+ `Fiber::Scheduler` works. `threads` in the manifest still counts docking links
91
+ per worker, so Mothership needs no change, but a parked fiber costs no thread,
92
+ so it can go much higher (64, 256).
93
+
94
+ - Fibers are not preempted: a request burning CPU stalls every other request
95
+ on its reactor. Size `workers` for CPU and `threads` for I/O wait.
96
+ `Shuttlebay.fiber_scheduler(reactors: 4) { … }` splits a worker's links
97
+ across 4 reactor threads, which Ruby does preempt, so one busy request
98
+ stalls a quarter of the links instead of all of them.
99
+ - Only I/O through Ruby's scheduler yields: Ruby sockets (`Net::HTTP`, Redis
100
+ clients without hiredis), `pg`, `sleep`. C extensions doing their own
101
+ blocking I/O (`mysql2`, libcurl gems) stall the reactor.
102
+ - Rails: set `config.active_support.isolation_level = :fiber` and size the
103
+ Active Record pool to at least `threads`.
104
+ - Hijacked connections (WebSockets) still relay on their own threads.
105
+ - Async's I/O hooks use `IO::Buffer`, so Ruby prints a one-time experimental
106
+ warning to stderr; `Warning[:experimental] = false` silences it.
107
+
108
+ ## Development server and system tests
109
+
110
+ ```bash
111
+ bin/rails server -u shuttlebay # or: rackup -s shuttlebay
112
+ ```
113
+
114
+ ```ruby
115
+ # test/application_system_test_case.rb
116
+ require "shuttlebay/capybara"
117
+ Capybara.server = :shuttlebay
118
+ ```
119
+
120
+ Both run the app in their own process and start a Mothership that attaches to
121
+ it, so development and tests go through the same HTTP stack as production. The
122
+ binary comes from `MOTHERSHIP_BIN`, the `guardship` gem, or `PATH`. The
123
+ `Threads` option (`rackup -O Threads=5`) or `MS_BAY_THREADS` sets the thread
124
+ count; Mothership sizes its link pool from what the app reports when it docks.
125
+
126
+ For HTTPS, run Mothership with a `tls` bind and the app as a docking upstream
127
+ (see the Mothership README) instead of the attached server.
128
+
129
+ ## Behaviour
130
+
131
+ - **Responses:** HEAD, 1xx, 204 and 304 send no body. `to_ary` bodies go out
132
+ in one write. `each` bodies flush per chunk (SSE, `ActionController::Live`).
133
+ Rack 3 streaming bodies (`call`) are supported. A `to_path` body with status
134
+ 200 is served by Mothership from disk.
135
+ - **Thread handoff:** after the response's last byte, the thread closes the
136
+ body and runs `rack.response_finished` callbacks, then sends `Ready`.
137
+ Mothership routes the next request to that link only after `Ready`, so a
138
+ request never waits behind Rails' after-response work.
139
+ - **Errors:** an exception before the head goes out becomes a 500. After the
140
+ head, the link is closed and the response aborts.
141
+ `rack.response_finished` callbacks run after every response.
142
+ - **WebSockets:** run ActionCable on [orbitcast](https://github.com/seuros/orbitcast),
143
+ which holds the sockets in Rust and speaks ActionCable's protocol, so no
144
+ Ruby thread sits on a connection. Every other WebSocket (Faye,
145
+ `websocket-driver`, custom protocols, or ActionCable's built-in server in
146
+ development and system tests) goes through `rack.hijack`: the app answers on
147
+ the hijacked IO, Mothership relays the upgraded connection, and the thread
148
+ goes back to serving while the relay runs on threads of its own.
149
+ - **Early hints:** not offered (`rack.early_hints` is absent). Mothership's
150
+ HTTP stack cannot send 1xx responses yet; Rails falls back to `Link`
151
+ headers on the final response.
152
+ - **Large uploads:** bodies over 1 MiB are spooled to an unlinked temp file
153
+ (`rack.input` is a `Tempfile`). Memory stays bounded however large uploads
154
+ are allowed to be.
155
+ - **Signals:** on `TERM`, workers stop accepting, drop idle links, and finish
156
+ in-flight requests within `MS_BAY_DRAIN_TIMEOUT` (Mothership passes the
157
+ bay's `request_timeout`). A dead worker is re-forked with backoff.
158
+ - **Deploys:** `kill -USR1` the mothership process to roll the bay to new code
159
+ with no dropped requests (phased restart).
160
+ - **TLS:** terminated by Mothership on a `tls` bind; the app sees
161
+ `rack.url_scheme = "https"`.
162
+ - **Logs:** JSON lines on stdout, which Mothership passes through.
163
+
164
+ ## Environment (set by Mothership)
165
+
166
+ `MS_SOCKET_PATH`, `MS_SHIP`, `MS_BAY_WORKERS`, `MS_BAY_THREADS`, `MS_BAY_DRAIN_TIMEOUT`.
167
+
168
+ ## Development
169
+
170
+ ```bash
171
+ bundle install
172
+ bundle exec rake compile # builds lib/shuttlebay/shuttlebay.{bundle,so}
173
+ bundle exec rake test
174
+ ```
175
+
176
+ The engine crate (`ext/shuttlebay`) shares the wire format with Mothership
177
+ through `mothership-docking-protocol` and is never published to crates.io.
178
+ Platform gems: `bundle exec rake platforms:build` (rb-sys-dock, Docker).
179
+
180
+ Releases: release-please tags the version, CI builds the platform and source
181
+ gems and attaches them to the GitHub release. Push them to rubygems.org from a
182
+ workstation:
183
+
184
+ ```bash
185
+ bundle exec rake 'gems:fetch[v0.1.0]' # download the release's gems into pkg/
186
+ bundle exec rake 'gems:push[v0.1.0]' # fetch, then gem push each one
187
+ ```
188
+
189
+ ## License
190
+
191
+ MIT
data/exe/shuttlebay ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/shuttlebay/logger'
5
+
6
+ begin
7
+ require_relative '../lib/shuttlebay/runtime'
8
+ Shuttlebay::Server.from_env(ARGV).run
9
+ rescue StandardError, ScriptError => e
10
+ Shuttlebay::Logger.new.error('shuttlebay stopped', **Shuttlebay::Logger.error_fields(e))
11
+ exit 1
12
+ end
@@ -0,0 +1,31 @@
1
+ [package]
2
+ name = "shuttlebay"
3
+ version = "0.1.4" # x-release-please-version
4
+ edition.workspace = true
5
+ rust-version.workspace = true
6
+ license.workspace = true
7
+ repository.workspace = true
8
+ authors = ["Abdelkader Boudih <oss@seuros.com>"]
9
+ description = "Ruby engine for Mothership HTTP bays: docking protocol codec and Rack env builder"
10
+ publish = false
11
+
12
+ [lib]
13
+ crate-type = ["cdylib"]
14
+
15
+ [lints.rust]
16
+ unsafe_code = "deny"
17
+ unused_must_use = "warn"
18
+
19
+ [lints.clippy]
20
+ all = { level = "warn", priority = -1 }
21
+ pedantic = { level = "warn", priority = -1 }
22
+ nursery = { level = "warn", priority = -1 }
23
+ unwrap_used = "deny"
24
+ expect_used = "deny"
25
+
26
+ [dependencies]
27
+ magnus = { version = "0.9", features = ["bytes"] }
28
+ rb-sys = "0.9.124"
29
+ mothership-docking-protocol = "0.2.0"
30
+ serde_json = "1.0"
31
+ state-machines = "0.30"