tonic-rails 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -1
- data/Cargo.lock +1 -49
- data/README.md +19 -0
- data/ext/tonic_rails_native/Cargo.toml +1 -2
- data/ext/tonic_rails_native/extconf.rb +28 -0
- data/ext/tonic_rails_native/src/client.rs +1 -1
- data/ext/tonic_rails_native/src/gvl.rs +3 -3
- data/ext/tonic_rails_native/src/lib.rs +1 -0
- data/ext/tonic_rails_native/src/server.rs +1 -1
- data/ext/tonic_rails_native/src/sync.rs +42 -0
- data/lib/tonic/rails/client.rb +42 -5
- data/lib/tonic/rails/version.rb +1 -1
- data/lib/tonic-rails.rb +4 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6cc5ee93380e004201e88f63289b88dff01dd6cc6566c92a3d972b9c934150d
|
|
4
|
+
data.tar.gz: 20a5a45adb019fa7275330b2d5b9f256a9f259765e1ffc83f4f1278902b38620
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 452568660e7a6f19c16d77ff8d272691c0f54c7ebf67261e5199dab6ad59f6c1df89b4b8e0efab84748e089bd74f4e4c2bf4de7dd599e628de0fc11f6ce03922
|
|
7
|
+
data.tar.gz: 52d5199c4d503c0792d84d254442a87cc25fc962b9af87561129c08c95974c48424c1a8914bd0cfcf664331d5c2f892c6cd8f31e507309bacb8c32b3ecc2fb53
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ minor versions may contain breaking changes.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-10-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Client: `method_retries:` sets retry policies per service
|
|
15
|
+
(`"demo.v1.ItemService"`) or per method
|
|
16
|
+
(`"demo.v1.ItemService/GetItem"`), like gRPC's service config. The most
|
|
17
|
+
specific setting wins: the call's `retries:`, then the method, then the
|
|
18
|
+
service, then the client's `retries:`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Windows: the process could crash with a segfault under load (#41). The
|
|
23
|
+
extension's locks spun and then called `Sleep`, which the mingw linker
|
|
24
|
+
bound to the `Sleep` that Ruby's DLL exports instead of the system's,
|
|
25
|
+
and Ruby's version crashes on threads Ruby doesn't own. Locks now come
|
|
26
|
+
from Rust's standard library, which doesn't spin through `Sleep`, the
|
|
27
|
+
build binds `Sleep` to kernel32, and CI checks that the Windows build
|
|
28
|
+
imports nothing but Ruby's C API from Ruby's DLL. 0.6.0 and 0.7.0 crashed in about 1 in 5 runs of the test suite;
|
|
29
|
+
earlier versions could crash in the same way, but rarely.
|
|
30
|
+
|
|
10
31
|
## [0.7.0] - 2026-10-11
|
|
11
32
|
|
|
12
33
|
### Added
|
|
@@ -186,7 +207,8 @@ First public release.
|
|
|
186
207
|
- Precompiled native gems for Linux (x86_64, aarch64; glibc and musl) and
|
|
187
208
|
macOS (x86_64, arm64), Ruby 3.3 to 4.0.
|
|
188
209
|
|
|
189
|
-
[Unreleased]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.
|
|
210
|
+
[Unreleased]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.8.0...HEAD
|
|
211
|
+
[0.8.0]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.7.0...v0.8.0
|
|
190
212
|
[0.7.0]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.6.0...v0.7.0
|
|
191
213
|
[0.6.0]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.5.0...v0.6.0
|
|
192
214
|
[0.5.0]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.4.0...v0.5.0
|
data/Cargo.lock
CHANGED
|
@@ -468,15 +468,6 @@ dependencies = [
|
|
|
468
468
|
"windows-link",
|
|
469
469
|
]
|
|
470
470
|
|
|
471
|
-
[[package]]
|
|
472
|
-
name = "lock_api"
|
|
473
|
-
version = "0.4.14"
|
|
474
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
475
|
-
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
|
476
|
-
dependencies = [
|
|
477
|
-
"scopeguard",
|
|
478
|
-
]
|
|
479
|
-
|
|
480
471
|
[[package]]
|
|
481
472
|
name = "log"
|
|
482
473
|
version = "0.4.34"
|
|
@@ -573,29 +564,6 @@ version = "0.2.1"
|
|
|
573
564
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
574
565
|
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
|
575
566
|
|
|
576
|
-
[[package]]
|
|
577
|
-
name = "parking_lot"
|
|
578
|
-
version = "0.12.5"
|
|
579
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
580
|
-
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
|
581
|
-
dependencies = [
|
|
582
|
-
"lock_api",
|
|
583
|
-
"parking_lot_core",
|
|
584
|
-
]
|
|
585
|
-
|
|
586
|
-
[[package]]
|
|
587
|
-
name = "parking_lot_core"
|
|
588
|
-
version = "0.9.12"
|
|
589
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
590
|
-
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|
591
|
-
dependencies = [
|
|
592
|
-
"cfg-if",
|
|
593
|
-
"libc",
|
|
594
|
-
"redox_syscall",
|
|
595
|
-
"smallvec",
|
|
596
|
-
"windows-link",
|
|
597
|
-
]
|
|
598
|
-
|
|
599
567
|
[[package]]
|
|
600
568
|
name = "percent-encoding"
|
|
601
569
|
version = "2.3.2"
|
|
@@ -708,15 +676,6 @@ version = "0.2.3"
|
|
|
708
676
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
709
677
|
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
710
678
|
|
|
711
|
-
[[package]]
|
|
712
|
-
name = "redox_syscall"
|
|
713
|
-
version = "0.5.18"
|
|
714
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
715
|
-
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
|
716
|
-
dependencies = [
|
|
717
|
-
"bitflags",
|
|
718
|
-
]
|
|
719
|
-
|
|
720
679
|
[[package]]
|
|
721
680
|
name = "regex"
|
|
722
681
|
version = "1.13.1"
|
|
@@ -822,12 +781,6 @@ dependencies = [
|
|
|
822
781
|
"windows-sys 0.61.2",
|
|
823
782
|
]
|
|
824
783
|
|
|
825
|
-
[[package]]
|
|
826
|
-
name = "scopeguard"
|
|
827
|
-
version = "1.2.0"
|
|
828
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
829
|
-
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
830
|
-
|
|
831
784
|
[[package]]
|
|
832
785
|
name = "security-framework"
|
|
833
786
|
version = "3.7.0"
|
|
@@ -1107,7 +1060,7 @@ dependencies = [
|
|
|
1107
1060
|
|
|
1108
1061
|
[[package]]
|
|
1109
1062
|
name = "tonic_rails_native"
|
|
1110
|
-
version = "0.
|
|
1063
|
+
version = "0.8.0"
|
|
1111
1064
|
dependencies = [
|
|
1112
1065
|
"axum",
|
|
1113
1066
|
"base64",
|
|
@@ -1118,7 +1071,6 @@ dependencies = [
|
|
|
1118
1071
|
"http-body",
|
|
1119
1072
|
"http-body-util",
|
|
1120
1073
|
"magnus",
|
|
1121
|
-
"parking_lot",
|
|
1122
1074
|
"prost",
|
|
1123
1075
|
"prost-types",
|
|
1124
1076
|
"rb-sys",
|
data/README.md
CHANGED
|
@@ -518,6 +518,24 @@ items.get_item({ id: 1 }, retries: false) # not this call
|
|
|
518
518
|
items.get_item({ id: 1 }, retries: { max_attempts: 5 }) # or differently
|
|
519
519
|
```
|
|
520
520
|
|
|
521
|
+
Different methods often need different policies: a read can retry more
|
|
522
|
+
eagerly than a write. `method_retries:` sets them per service or per
|
|
523
|
+
method, by their protobuf names:
|
|
524
|
+
|
|
525
|
+
```ruby
|
|
526
|
+
ITEMS = Tonic::Rails::Client.new(
|
|
527
|
+
"items.internal:50051",
|
|
528
|
+
retries: true, # every call...
|
|
529
|
+
method_retries: {
|
|
530
|
+
"demo.v1.ItemService/GetItem" => { max_attempts: 5 }, # ...but this method,
|
|
531
|
+
"demo.v1.AdminService" => false, # and this whole service
|
|
532
|
+
},
|
|
533
|
+
)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
The most specific setting wins: the call's own `retries:`, then its
|
|
537
|
+
method's entry, then its service's, then the client's `retries:`.
|
|
538
|
+
|
|
521
539
|
`retries: true` tries a call up to 3 times when it fails with
|
|
522
540
|
`UNAVAILABLE`. A Hash changes any of these:
|
|
523
541
|
|
|
@@ -612,6 +630,7 @@ the client is created, not on the first call.
|
|
|
612
630
|
| `max_message_size` | 4 MiB | largest message sent or received |
|
|
613
631
|
| `connect_timeout` | 5 | seconds to wait for a connection |
|
|
614
632
|
| `retries` | `false` | retry calls the server never started on; see [Retries](#retries) |
|
|
633
|
+
| `method_retries` | `{}` | retries per service or method, by name |
|
|
615
634
|
| `logger` | `Tonic::Rails.configuration.logger` | where retries are logged |
|
|
616
635
|
|
|
617
636
|
### Fork safety
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "tonic_rails_native"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.88"
|
|
6
6
|
publish = false
|
|
@@ -41,7 +41,6 @@ tokio-stream = { version = "0.1", features = ["net"] }
|
|
|
41
41
|
prost = "0.14"
|
|
42
42
|
prost-types = "0.14"
|
|
43
43
|
socket2 = { version = "0.6", features = ["all"] }
|
|
44
|
-
parking_lot = "0.12"
|
|
45
44
|
crossbeam-channel = "0.5"
|
|
46
45
|
http = "1"
|
|
47
46
|
http-body = "1"
|
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
3
4
|
require "mkmf"
|
|
4
5
|
require "rb_sys/mkmf"
|
|
6
|
+
require "shellwords"
|
|
7
|
+
|
|
8
|
+
# On mingw, Ruby's DLL exports its own `Sleep`, which releases the GVL
|
|
9
|
+
# around the wait: on a thread Ruby doesn't own it crashes, and on a Ruby
|
|
10
|
+
# thread that has already released the GVL it takes the GVL back from
|
|
11
|
+
# whichever thread holds it (#41). rb-sys links Ruby's import library ahead of
|
|
12
|
+
# kernel32, so the extension's `Sleep` (from mingw's DLL startup code, and
|
|
13
|
+
# from std via crossbeam) would bind to Ruby's. Link a one-function import
|
|
14
|
+
# library binding `Sleep` to kernel32 ahead of Ruby's instead. (Putting
|
|
15
|
+
# all of kernel32 first goes too far: some toolchains' kernel32 import
|
|
16
|
+
# libraries claim functions kernel32.dll doesn't export.)
|
|
17
|
+
# script/check_windows_imports.rb checks the result in CI.
|
|
18
|
+
if RbConfig::CONFIG["target_os"].to_s.include?("mingw")
|
|
19
|
+
dir = File.expand_path("kernel32_sleep")
|
|
20
|
+
FileUtils.mkdir_p(dir)
|
|
21
|
+
File.write(File.join(dir, "sleep.def"), "LIBRARY kernel32.dll\nEXPORTS\nSleep\n")
|
|
22
|
+
# Cross-compiling, RbConfig describes the target, so its DLLTOOL may not
|
|
23
|
+
# exist here: try the one next to the C compiler too.
|
|
24
|
+
compiler = Shellwords.split(RbConfig::CONFIG["CC"].to_s).find { |word| word.include?("gcc") }.to_s
|
|
25
|
+
candidates = [RbConfig::CONFIG["DLLTOOL"], compiler.sub(/gcc(\.exe)?\z/, "dlltool"), "dlltool"]
|
|
26
|
+
dlltool = candidates.reject { |tool| tool.to_s.empty? }.uniq.find { |tool| find_executable(tool) }
|
|
27
|
+
abort "tonic-rails: no dlltool found (tried #{candidates.compact.uniq.join(', ')})" unless dlltool
|
|
28
|
+
unless system(dlltool, "-d", File.join(dir, "sleep.def"), "-l", File.join(dir, "libtonic_rails_sleep.a"))
|
|
29
|
+
abort "tonic-rails: #{dlltool} failed to build the kernel32 Sleep import library"
|
|
30
|
+
end
|
|
31
|
+
RbConfig::CONFIG["LIBS"] = "-L#{Shellwords.escape(dir)} -ltonic_rails_sleep #{RbConfig::CONFIG['LIBS']}"
|
|
32
|
+
end
|
|
5
33
|
|
|
6
34
|
# Builds the Rust crate with cargo and installs the library as
|
|
7
35
|
# lib/tonic/rails/tonic_rails_native.{so,bundle}, matching
|
|
@@ -24,7 +24,6 @@ use bytes::{Buf, BufMut, Bytes};
|
|
|
24
24
|
use http::uri::PathAndQuery;
|
|
25
25
|
use http::{HeaderMap, HeaderName, HeaderValue};
|
|
26
26
|
use magnus::{Error, RArray, RHash, RString, Ruby, r_hash::ForEach};
|
|
27
|
-
use parking_lot::Mutex;
|
|
28
27
|
use tokio::runtime::{Builder, Handle, Runtime};
|
|
29
28
|
use tokio::sync::mpsc;
|
|
30
29
|
use tokio::task::JoinHandle;
|
|
@@ -34,6 +33,7 @@ use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity
|
|
|
34
33
|
use tonic::{Code, Request, Status};
|
|
35
34
|
|
|
36
35
|
use crate::gvl::{Cancel, Wait, block_on, recv_blocking, without_gvl};
|
|
36
|
+
use crate::sync::Mutex;
|
|
37
37
|
|
|
38
38
|
// ─── Per-process runtime ──────────────────────────────────────────
|
|
39
39
|
|
|
@@ -16,11 +16,11 @@ use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
|
|
16
16
|
use std::task::{Context, Poll, Wake, Waker};
|
|
17
17
|
|
|
18
18
|
use crate::log::{Level, LogSink};
|
|
19
|
+
use crate::sync::{Condvar, Mutex};
|
|
19
20
|
use magnus::{
|
|
20
21
|
Error, RHash, RString, Ruby, Value, prelude::*, r_hash::ForEach, typed_data::DataTypeFunctions,
|
|
21
22
|
value::Opaque,
|
|
22
23
|
};
|
|
23
|
-
use parking_lot::{Condvar, Mutex};
|
|
24
24
|
use rb_sys::{RB_NOGVL_INTR_FAIL, rb_nogvl, rb_thread_call_with_gvl};
|
|
25
25
|
use tokio::sync::{Notify, mpsc};
|
|
26
26
|
|
|
@@ -101,7 +101,7 @@ where
|
|
|
101
101
|
/// Minimal single-future executor that parks the current (non-tokio)
|
|
102
102
|
/// thread. tokio's channel and `Notify` futures are runtime-agnostic.
|
|
103
103
|
///
|
|
104
|
-
/// It parks on a
|
|
104
|
+
/// It parks on a condition variable rather than
|
|
105
105
|
/// `std::thread::park`, which on macOS uses libdispatch: libdispatch
|
|
106
106
|
/// traps in the child of a multi-threaded process, and the client blocks
|
|
107
107
|
/// here in processes forked after it made calls.
|
|
@@ -131,7 +131,7 @@ pub fn block_on<F: Future>(fut: F) -> F::Output {
|
|
|
131
131
|
}
|
|
132
132
|
let mut woken = signal.woken.lock();
|
|
133
133
|
while !*woken {
|
|
134
|
-
signal.condvar.wait(
|
|
134
|
+
woken = signal.condvar.wait(woken);
|
|
135
135
|
}
|
|
136
136
|
*woken = false;
|
|
137
137
|
}
|
|
@@ -22,7 +22,6 @@ use std::time::{Duration, Instant};
|
|
|
22
22
|
use magnus::{
|
|
23
23
|
Error, RArray, RHash, RString, Ruby, TryConvert, Value, gc, typed_data::DataTypeFunctions, value::Opaque,
|
|
24
24
|
};
|
|
25
|
-
use parking_lot::Mutex;
|
|
26
25
|
use prost::Message;
|
|
27
26
|
use tokio::runtime::Runtime;
|
|
28
27
|
use tokio::sync::{Semaphore, watch};
|
|
@@ -37,6 +36,7 @@ use crate::dispatch::{DispatchInner, DispatchService};
|
|
|
37
36
|
use crate::gvl::{self, Cancel, DispatchContext};
|
|
38
37
|
use crate::log::{Level, LogEvent, LogSink};
|
|
39
38
|
use crate::net::{LimitedIncoming, bind, listen, listen_inherited};
|
|
39
|
+
use crate::sync::Mutex;
|
|
40
40
|
|
|
41
41
|
/// The overall-server health entry (empty service name).
|
|
42
42
|
const OVERALL: &str = "";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//! `Mutex` and `Condvar` over `std::sync`, with parking_lot's API: `lock`
|
|
2
|
+
//! returns the guard, and a panic while holding a lock doesn't poison it.
|
|
3
|
+
//!
|
|
4
|
+
//! Why not parking_lot: under contention it spins and then calls `Sleep`,
|
|
5
|
+
//! and on Windows (mingw) the extension's `Sleep` import binds to the one
|
|
6
|
+
//! Ruby's DLL exports. Ruby's `Sleep` expects a Ruby thread, so on a tokio
|
|
7
|
+
//! thread it crashed the process (#41). std's locks use SRW locks and
|
|
8
|
+
//! never call `Sleep`.
|
|
9
|
+
|
|
10
|
+
use std::sync::{MutexGuard, PoisonError};
|
|
11
|
+
|
|
12
|
+
#[derive(Default)]
|
|
13
|
+
pub struct Mutex<T>(std::sync::Mutex<T>);
|
|
14
|
+
|
|
15
|
+
impl<T> Mutex<T> {
|
|
16
|
+
pub const fn new(value: T) -> Self {
|
|
17
|
+
Self(std::sync::Mutex::new(value))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fn lock(&self) -> MutexGuard<'_, T> {
|
|
21
|
+
self.0.lock().unwrap_or_else(PoisonError::into_inner)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn get_mut(&mut self) -> &mut T {
|
|
25
|
+
self.0.get_mut().unwrap_or_else(PoisonError::into_inner)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Default)]
|
|
30
|
+
pub struct Condvar(std::sync::Condvar);
|
|
31
|
+
|
|
32
|
+
impl Condvar {
|
|
33
|
+
/// Block until notified, releasing `guard` meanwhile. Wakes can be
|
|
34
|
+
/// spurious: wait in a loop on the condition.
|
|
35
|
+
pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
|
|
36
|
+
self.0.wait(guard).unwrap_or_else(PoisonError::into_inner)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn notify_one(&self) {
|
|
40
|
+
self.0.notify_one();
|
|
41
|
+
}
|
|
42
|
+
}
|
data/lib/tonic/rails/client.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Tonic
|
|
|
16
16
|
#
|
|
17
17
|
# Retries are opt-in: `retries: true` (or a Hash; see RetryPolicy) on
|
|
18
18
|
# the client or a call retries calls the server never started on.
|
|
19
|
+
# `method_retries:` sets them per service or method.
|
|
19
20
|
#
|
|
20
21
|
# Stubs are built from the protobuf descriptors that
|
|
21
22
|
# `tonic_rails:compile_protos` (protoc --ruby_out) generates; no
|
|
@@ -46,6 +47,10 @@ module Tonic
|
|
|
46
47
|
# @return [RetryPolicy, nil] how calls are retried, unless they say otherwise
|
|
47
48
|
attr_reader :retries
|
|
48
49
|
|
|
50
|
+
# @return [Hash{String=>RetryPolicy, nil}] retry policies by service or
|
|
51
|
+
# method name, which take precedence over #retries
|
|
52
|
+
attr_reader :method_retries
|
|
53
|
+
|
|
49
54
|
# @param address [String] "host:port"
|
|
50
55
|
# @param tls [Boolean] false for plaintext, which must be explicit
|
|
51
56
|
# @param tls_ca_path [String, nil] CA certificates (PEM) to trust instead
|
|
@@ -61,11 +66,15 @@ module Tonic
|
|
|
61
66
|
# @param connect_timeout [Numeric] seconds to wait for a connection
|
|
62
67
|
# @param retries [RetryPolicy, Hash, Boolean] retry calls the server
|
|
63
68
|
# never started on (see RetryPolicy); off by default
|
|
69
|
+
# @param method_retries [Hash{String=>RetryPolicy, Hash, Boolean}]
|
|
70
|
+
# retries for some services ("demo.v1.ItemService") or methods
|
|
71
|
+
# ("demo.v1.ItemService/GetItem"), instead of `retries`. A method's
|
|
72
|
+
# entry beats its service's.
|
|
64
73
|
# @param logger [Logger, nil] where retries are logged; defaults to
|
|
65
74
|
# Tonic::Rails.configuration.logger
|
|
66
75
|
def initialize(address, tls: true, tls_ca_path: nil, tls_cert_path: nil, tls_key_path: nil,
|
|
67
76
|
tls_server_name: nil, deadline: nil, metadata: {}, max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
|
|
68
|
-
connect_timeout: 5, retries: false, logger: nil)
|
|
77
|
+
connect_timeout: 5, retries: false, method_retries: {}, logger: nil)
|
|
69
78
|
Tonic::Rails.native_extension!
|
|
70
79
|
validate_address!(address)
|
|
71
80
|
validate_tls!(tls, tls_cert_path, tls_key_path)
|
|
@@ -78,6 +87,7 @@ module Tonic
|
|
|
78
87
|
@address = address
|
|
79
88
|
@deadline = deadline
|
|
80
89
|
@retries = RetryPolicy.from(retries)
|
|
90
|
+
@method_retries = method_retry_policies(method_retries)
|
|
81
91
|
@logger = logger
|
|
82
92
|
@metadata = Metadata.encode(metadata)
|
|
83
93
|
@stubs = {}
|
|
@@ -127,7 +137,7 @@ module Tonic
|
|
|
127
137
|
bytes = input.encode(request_message(path, input, request))
|
|
128
138
|
metadata = call_metadata(metadata)
|
|
129
139
|
deadline_at = deadline_at(deadline)
|
|
130
|
-
policy = retry_policy(retries)
|
|
140
|
+
policy = retry_policy(path, retries)
|
|
131
141
|
|
|
132
142
|
Instrumentation.instrument_request(path, address, metadata) do |payload|
|
|
133
143
|
code, error_message, response, headers, trailers, details = with_retries(path, policy, deadline_at, payload) do
|
|
@@ -150,7 +160,7 @@ module Tonic
|
|
|
150
160
|
# Fixed now, so the stream keeps the deadline of the RPC this
|
|
151
161
|
# thread is handling even if it's read later.
|
|
152
162
|
deadline_at = deadline_at(deadline)
|
|
153
|
-
policy = retry_policy(retries)
|
|
163
|
+
policy = retry_policy(path, retries)
|
|
154
164
|
|
|
155
165
|
ServerStream.new(path) do |stream, block|
|
|
156
166
|
Instrumentation.instrument_request(path, address, metadata) do |payload|
|
|
@@ -238,8 +248,35 @@ module Tonic
|
|
|
238
248
|
(remaining * 1000).ceil
|
|
239
249
|
end
|
|
240
250
|
|
|
241
|
-
|
|
242
|
-
|
|
251
|
+
METHOD_NAME = %r{\A[A-Za-z_][\w.]*(/[A-Za-z_]\w*)?\z}
|
|
252
|
+
private_constant :METHOD_NAME
|
|
253
|
+
|
|
254
|
+
def method_retry_policies(option)
|
|
255
|
+
raise ArgumentError, "method_retries must be a Hash" unless option.is_a?(Hash)
|
|
256
|
+
|
|
257
|
+
option.to_h do |name, retries|
|
|
258
|
+
name = name.to_s.delete_prefix("/")
|
|
259
|
+
unless name.match?(METHOD_NAME)
|
|
260
|
+
raise ArgumentError, "method_retries keys must be \"package.Service\" or \"package.Service/Method\", " \
|
|
261
|
+
"got #{name.inspect}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
[name, RetryPolicy.from(retries)]
|
|
265
|
+
end.freeze
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# The most specific policy for a call: its own, its method's, its
|
|
269
|
+
# service's, then the client's.
|
|
270
|
+
def retry_policy(path, retries)
|
|
271
|
+
return RetryPolicy.from(retries) unless retries.nil?
|
|
272
|
+
return @retries if @method_retries.empty?
|
|
273
|
+
|
|
274
|
+
method = path.delete_prefix("/")
|
|
275
|
+
service = method.split("/", 2).first
|
|
276
|
+
return @method_retries[method] if @method_retries.key?(method)
|
|
277
|
+
return @method_retries[service] if @method_retries.key?(service)
|
|
278
|
+
|
|
279
|
+
@retries
|
|
243
280
|
end
|
|
244
281
|
|
|
245
282
|
# Yield each message of a native stream, then return its result.
|
data/lib/tonic/rails/version.rb
CHANGED
data/lib/tonic-rails.rb
CHANGED
|
@@ -81,11 +81,13 @@ module Tonic
|
|
|
81
81
|
# Precompiled platform gems ship one binary per Ruby minor version.
|
|
82
82
|
ruby_version = RUBY_VERSION[/\d+\.\d+/]
|
|
83
83
|
require "tonic/rails/#{ruby_version}/tonic_rails_native"
|
|
84
|
-
rescue LoadError
|
|
84
|
+
rescue LoadError => versioned
|
|
85
85
|
begin
|
|
86
86
|
require "tonic/rails/tonic_rails_native"
|
|
87
87
|
rescue LoadError => e
|
|
88
|
-
|
|
88
|
+
# A precompiled binary that exists but won't load says more than
|
|
89
|
+
# the missing fallback does.
|
|
90
|
+
@native_load_error = versioned.message.include?("cannot load such file") ? e : versioned
|
|
89
91
|
end
|
|
90
92
|
end
|
|
91
93
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tonic-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Tanous
|
|
@@ -81,6 +81,7 @@ files:
|
|
|
81
81
|
- ext/tonic_rails_native/src/log.rs
|
|
82
82
|
- ext/tonic_rails_native/src/net.rs
|
|
83
83
|
- ext/tonic_rails_native/src/server.rs
|
|
84
|
+
- ext/tonic_rails_native/src/sync.rs
|
|
84
85
|
- lib/generators/tonic_rails/install/install_generator.rb
|
|
85
86
|
- lib/generators/tonic_rails/install/templates/tonic_rails.rb.tt
|
|
86
87
|
- lib/generators/tonic_rails/service/service_generator.rb
|