github.com-facebook-fbthrift
all · 66 devs · built 2026-06-13
Repository snapshot
Monthly reports
Highlights
- Introduced new introspection capabilities for *Thrift Python3 runtime* with `get_locally_set_fields` API, replacing `isset_DEPRECATED` [025631f3 · Andrew Hilger], [01db3940 · Andrew Hilger].
- Enhanced *Thrift C++2 fast_thrift* with robust backpressure handling for `IntervalBatchingFrameHandler` [bfd013cf · Ankit Kumar] and a new `WriteBufferBackpressureHandler` for end-to-end flow control [e672c6ae · Ankit Kumar].
- Enabled `IORING_RECVSEND_BUNDLE` for the *io_uring backend*, significantly reducing completion overhead for high-volume network I/O [69e51959 · Clément Léger].
- Implemented `StopTLSv2` optimizations in *FizzPeeker* and enabled `Fizz TLSv2` read/write optimizations in *stresstest client and server* for more realistic performance benchmarking [a9c89b5c · David Wei], [801d4231 · David Wei].
- Introduced `CompositeMovingCounter` in the *Thrift Java runtime metrics library* for lock-free, high-performance rate calculations, leading to an order-of-magnitude memory reduction [acc29470 · Jeff Bahr], [2909f193 · Jeff Bahr].
- Extended the `@cpp.Frozen2Exclude` annotation scope to individual *fields*, providing more granular control over frozen layout [21b331d2 · Uday Garikipati].
Observations
- Commit volume decreased significantly by 33% this month (390 commits) compared to the 2-month average of 581 commits.
- Waste score saw a notable decrease of 29% (5 current vs 7 2-month average), indicating improved code quality and reduced rework.
- Despite the lower commit count, both Grow and Maintenance scores increased by 15% each compared to the 2-month average, suggesting a higher impact per commit.
- Several critical bug fixes were addressed, including a `SIGSEGV` in the *Thrift stresstest client* related to `StopTLSv2` [efddd54c · David Wei], a `SIGSEGV` in *Thrift BiDi client streams* due to null dereference [a6c1e690 · Evan Zou], and a fix for `fast_thrift Rocket client` stream ID collisions [419f20d0 · Ankit Kumar].
- Extensive refactoring and modernization efforts were observed across *Thrift Python* asynchronous utilities and *Thrift C++2 server* components, including consolidating encoding logic [4037fa38 · TJ Yin], making pipeline activation idempotent [b018ed5d · Ankit Kumar], and improving server pipeline lifecycle management [7e7fdbb5 · Ankit Kumar].
- Multiple commits focused on enhancing the robustness and correctness of the *fast_thrift* framework, particularly around fragmentation handlers [9cf0a209 · Ankit Kumar], connection lifecycle [5b55d745 · Ankit Kumar], and error propagation in batching handlers [98a7eef5 · Ankit Kumar].
- Performance regressions were addressed, such as preserving zero-copy state across `StopTLS` plaintext transitions to reduce CPU overhead [04e72d1d · Anand Natarajan].
Performance over time
ETV stacked by Growth, Maintenance and Fixes — 90-day moving average, normalized to ETV / month.
Average performance per developer
ETV per active developer per month — 30-day moving average.
Active developers over time
Unique developers committing each day — 90-day moving average.
Knowledge concentration
How dependent is this repo on a small number of contributors? Higher top-1 share = higher key-person risk.
Jack Chistyakov owns 13.9 % of commits.
Top contributors
Most impactful commits
Top 20 by ETV in the all-time window.
- 4.3ETVMove `thrift/facebook/json5/*` to `thrift/lib/cpp2/protocol` Summary: This diff moved all files under `thrift/facebook/json5 to `thrift/lib/cpp2/protocol`: * `Json5Protocol.h` is moved to `thrift/lib/cpp2/protocol`. Other files are moved to `thrift/lib/cpp2/protocol/detail`. * `thrift/facebook/json5/test` is moved to `thrift/lib/cpp2/protocol/test` * `thrift/facebook/json5/test/example.thrift` is renamed to `json5_test.thrift` Reviewed By: aristidisp Differential Revision: D95818321 fbshipit-source-id: 19d443e93b3a5ea5965b2e62893407874c9634e2TJ Yin · aadfdb31 · 2026-03-10
- 3.3ETVWhitespace control Summary: Add tilde whitespace trimming (`{{~ ~}}`) to the Whisker template language, giving template authors fine-grained control over whitespace in generated code. Whisker templates generate source code across 12 languages, but producing correctly-formatted output has required pervasive workarounds. **282+ template files** use the `{{!` comment hack — empty comments inserted solely to suppress unwanted newlines and whitespace. The result is templates like this, where the actual logic is buried under noise: ```mustache #[derive({{! }}{{#if struct:copy?}}Copy, {{/if struct:copy?}}{{! }}Clone, PartialEq{{! }}{{#if struct:ord?}}, Eq, PartialOrd, Ord, Hash{{/if struct:ord?}}{{! }}{{#if struct:serde?}}, ::serde_derive::Serialize{{/if struct:serde?}}{{! }}) ``` With tilde trimming, the same template becomes readable at a glance: ```mustache #[derive( {{~ #if struct:copy? }}Copy, {{/if struct:copy? ~}} Clone, PartialEq {{~ #if struct:ord? }}, Eq, PartialOrd, Ord, Hash{{/if struct:ord? ~}} {{~ #if struct:serde? }}, ::serde_derive::Serialize{{/if struct:serde? ~}} )] ``` See whisker.md for more details. Reviewed By: hchokshi Differential Revision: D97992973 fbshipit-source-id: 9a9d6a9738fc6fdfa53f8467c1c7205c65defd24Pranjal Raihan · 66ec2ab4 · 2026-04-08
- 3.1ETVImprove MonoTimeoutTransformer's reactive compliance and multithreaded safety Summary: # MonoTimeoutTransformer: Complete Rewrite for Reactive Streams Compliance ## Summary: This diff is a complete rewrite of MonoTimeoutTransformer to fix multiple race conditions, ensure Reactive Streams specification compliance, and add comprehensive test coverage. ## Why This Rewrite Was Necessary The original implementation had fundamental design flaws: 1. **No explicit state machine**: Used `isDisposed()` checks which are not atomic with respect to signal emission, allowing races. 2. **Incorrect fallback subscription**: Subscribed fallback directly to `actual::onNext, actual::onError, actual::onComplete` method references, bypassing cancellation checks entirely. 3. **Race conditions**: Multiple unhandled races between: - Source emission vs timeout firing (Race 1) - Cancel vs fallback subscription setup (Race 2) - Cancel after timeout but before scheduler execution (Race 3) 4. **No timer cleanup verification**: Timer cancellation on normal completion was not guaranteed. 5. **Untestable**: Timer was hardcoded, making deterministic race testing impossible. ## The Three Critical Race Conditions ### Race 1: Source vs Timeout (Signal Race) ``` Source Thread Timer Thread │ │ │── onNext() ──────────────────────────│── run(Timeout) ── │ [want to emit value] │ [want to emit error] │ │ └──────────── WHO WINS? ───────────────┘ ``` **Old behavior**: Both could win, potentially emitting both value and error. **New behavior**: Atomic CAS on STATE ensures exactly one winner. ### Race 2: Cancel vs Fallback Setup ``` Scheduler Thread Downstream Thread │ │ │─ fallback.subscribe(actual) ────────────────▶│ │ │── cancel() ── │ │ [too late?] ``` **Old behavior**: Fallback subscription ignored cancellation. **New behavior**: FallbackSubscriber uses Operators.set() to atomically check for cancellation before accepting the subscription. ### Race 3: Cancel After Timeout, Before Scheduler (TIMEOUT_MARKER Fix) ``` Timer Thread Downstream Thread Scheduler Thread │ │ │ │─ run(Timeout) ──────────▶│ │ │ [S → ???] │ │ │ [schedule(this)] │ │ │ │ │ │ │── cancel() ──────────────▶│ │ │ [Operators.terminate(S)]│ │ │ [did it work?] │ │ │ │ │ │ │─ run() ── │ │ │ [should skip?] ``` **Old behavior (if it existed)**: Would use `cancelledSubscription()` as marker in run(Timeout). But `cancel()` also uses `cancelledSubscription()`, so `Operators.terminate()` would see S as already cancelled and do nothing. The scheduler's `run()` would then proceed to emit signals to a cancelled subscriber. **New behavior**: Introduced `TIMEOUT_MARKER` - a distinct sentinel that marks "timeout fired, transitioning to fallback." This allows: 1. `run(Timeout)` sets S to TIMEOUT_MARKER (not cancelledSubscription) 2. If `cancel()` is called, `Operators.terminate()` successfully changes S from TIMEOUT_MARKER to cancelledSubscription() 3. `run()` uses CAS to check if S is still TIMEOUT_MARKER - if not, cancellation occurred and we skip signal emission ## Architecture Overview ### Old Architecture (Flawed) ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ MonoTimeoutTransformer (Old) │ │ │ │ ┌─────────────┐ ┌──────────────────────┐ ┌─────────────────────────┐ │ │ │ Source │───▶│ TimeoutSubscription │───▶│ Downstream │ │ │ │ Mono │ │ (just a holder) │ │ (CoreSubscriber) │ │ │ └─────────────┘ └──────────┬───────────┘ └─────────────────────────┘ │ │ │ │ │ │ creates │ │ ▼ │ │ ┌──────────────────────┐ │ │ │ SourceSubscriber │ ◀── extends BaseSubscriber │ │ │ (no state machine) │ (uses isDisposed()) │ │ └──────────────────────┘ │ │ │ │ Problems: │ │ - No atomic state management │ │ - Fallback subscribed via method references (no cancel check) │ │ - Race conditions between timeout/source/cancel │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### New Architecture (Correct) ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ │ MonoTimeoutTransformer (New) │ │ │ │ ┌─────────────┐ ┌───────────────────┐ ┌────────────────────────────┐ │ │ │ Source │─────▶│ TimeoutSubscriber │─────▶│ Downstream │ │ │ │ Mono │ │ (CoreSubscriber,│ │ (CoreSubscriber) │ │ │ └─────────────┘ │ Subscription, │ └────────────────────────────┘ │ │ │ TimerTask, │ │ │ │ Runnable) │ │ │ └────────┬──────────┘ │ │ │ │ │ ┌────────────────────┼────────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────────┐ │ │ │ STATE (atomic) │ │ S (atomic) │ │ FallbackSubscriber │ │ │ │ INIT=0 │ │ source sub │ │ (defensive cancel checks) │ │ │ │ VALUE_EMITTED=1│ │ TIMEOUT_MARKER │ │ - checks S before onNext │ │ │ │ TERMINATED=2 │ │ fallback sub │ │ - checks S before onError │ │ │ └─────────────────┘ │ CANCELLED │ │ - checks S before onComplete │ │ │ └─────────────────┘ └─────────────────────────────────┘ │ │ │ │ Key invariants: │ │ - STATE transitions are atomic (CAS) │ │ - Only one thread can win INIT → VALUE_EMITTED or INIT → TERMINATED │ │ - S tracks active subscription and cancellation state │ │ - TIMEOUT_MARKER enables Race 3 detection │ └─────────────────────────────────────────────────────────────────────────────────┘ ``` ## State Machine ``` ┌───────────────────────────────────────────────────┐ │ │ ▼ │ ┌──────────┐ │ │ INIT │◀─────────────────────────────────────────────┘ │ (0) │ (initial state) └────┬─────┘ │ ┌─────────┴─────────┬──────────────────┬──────────────────┐ │ │ │ │ │ onNext() │ timeout fires │ onComplete() │ cancel() │ [CAS succeeds] │ [CAS succeeds] │ [CAS succeeds] │ [getAndSet] ▼ │ │ │ ┌─────────────┐ │ │ │ │VALUE_EMITTED│ │ │ │ │ (1) │ │ │ │ └──────┬──────┘ │ │ │ │ │ │ │ │ onComplete() │ │ │ │ [CAS succeeds] │ │ │ ▼ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────┐ │ TERMINATED (2) │ │ (terminal state - no further state transitions allowed) │ └─────────────────────────────────────────────────────────────┘ ``` ## Key Implementation Changes ### 1. Atomic State Management (Race 1 Fix) The original code had no coordination between source signals and timeout. Both could proceed simultaneously, potentially delivering multiple terminal signals. **Before - Source emits value:** ```java Override protected void hookOnNext(T value) { timeout.cancel(); // Cancel timer, but might be too late actual.onNext(value); // Emit value unconditionally } ``` **Before - Timeout fires:** ```java private void doTimeout(Timeout timeout) { if (!isDisposed() && !timeout.isCancelled()) { // Check disposed doTimeout(); // But source might emit between check and here! } } private void doTimeoutException() { TimeoutException e = new TimeoutException("..."); scheduler.schedule(() -> actual.onError(e)); // Emit error } ``` **The Race:** If source emits while timeout fires: 1. `hookOnNext` calls `timeout.cancel()` - but timer callback already started 2. `doTimeout` checks `isDisposed()` → false (source hasn't finished yet) 3. Both `actual.onNext(value)` AND `actual.onError(e)` can execute 4. Downstream receives BOTH value and error → violates Reactive Streams spec **After - Both paths compete via atomic CAS:** ```java Override public void onNext(T t) { // CAS: Only ONE thread can transition INIT → VALUE_EMITTED if (STATE.compareAndSet(this, STATE_INIT, STATE_VALUE_EMITTED)) { cancelTimer(); actual.onNext(t); } else { // Lost the race - drop the value Operators.onNextDropped(t, actual.currentContext()); } } Override // TimerTask.run - called by timer thread public void run(Timeout timeout) { // CAS: Only ONE thread can transition INIT → TERMINATED if (STATE.compareAndSet(this, STATE_INIT, STATE_TERMINATED)) { // Won the race - cancel source and emit timeout Subscription current = S.getAndSet(this, TIMEOUT_MARKER); if (current != null) current.cancel(); scheduler.schedule(this); // Will emit error or subscribe fallback } // If CAS fails, source already won - do nothing } ``` **Why this works:** The CAS operation is atomic. Exactly one of source or timeout can successfully change STATE from INIT. The loser's CAS returns false, and they drop their signal. ### 2. Atomic Subscription Management (Race 2 & 3 Fix) The original code had no atomic tracking of subscription state. The new code uses an atomic `S` field to track the active subscription and detect cancellation. **Before:** ```java private static class TimeoutSubscription<T> implements Subscription { private SourceSubscriber<T> sourceSubscriber; // Not atomic! Override public void cancel() { if (sourceSubscriber != null) { sourceSubscriber.dispose(); // Just sets a boolean } } } ``` **Problem:** `dispose()` sets an internal boolean, but this isn't atomic with respect to subscription switching. If cancel() races with fallback subscription setup: - `dispose()` might set the boolean after fallback checks it - Or before fallback even exists **After:** ```java volatile Subscription s; static final AtomicReferenceFieldUpdater<TimeoutSubscriber, Subscription> S = ...; // TIMEOUT_MARKER: distinct sentinel for timeout-in-progress state static final Subscription TIMEOUT_MARKER = new Subscription() { ... }; Override // TimerTask.run public void run(Timeout timeout) { if (STATE.compareAndSet(this, STATE_INIT, STATE_TERMINATED)) { // Set S to TIMEOUT_MARKER (not cancelledSubscription!) Subscription current = S.getAndSet(this, TIMEOUT_MARKER); if (current != null) current.cancel(); scheduler.schedule(this); } } Override // Runnable.run - scheduler thread public void run() { // Race 3 detection: if cancel() was called, S is now cancelledSubscription if (s == Operators.cancelledSubscription()) { return; // Cancel won - don't emit anything } // CAS to detect late cancel: TIMEOUT_MARKER → null if (S.compareAndSet(this, TIMEOUT_MARKER, null)) { fallback.subscribe(new FallbackSubscriber<>(actual, this)); } // If CAS failed, cancel() changed S to cancelledSubscription - skip fallback } ``` **Why TIMEOUT_MARKER?** If we used `cancelledSubscription()` in run(Timeout): - `cancel()` calls `Operators.terminate(S, this)` which does: `S.getAndSet(this, cancelledSubscription())` - If S is already `cancelledSubscription()`, this is a no-op! - The scheduler's `run()` would have no way to detect the cancel With TIMEOUT_MARKER: - `cancel()` successfully changes S from TIMEOUT_MARKER → cancelledSubscription() - The scheduler's CAS from TIMEOUT_MARKER → null fails - Cancel is detected, fallback is skipped ### 3. Proper Fallback Handling (Race 2 Fix) The original code subscribed to fallback using method references, which bypassed all cancellation checks. **Before:** ```java private void doTimeoutFallback() { scheduler.schedule(() -> { try { // WRONG: Method references go directly to downstream! // If cancel() is called, these lambdas still execute! fallback.subscribe(actual::onNext, actual::onError, actual::onComplete); } catch (Throwable t) { actual.onError(t); } }); } ``` **Problem:** `actual::onNext` is just a method reference - it has no cancellation check. Even after `cancel()`, fallback signals go directly to downstream. **After:** ```java Override public void run() { // Check cancellation before any signal emission if (s == Operators.cancelledSubscription()) { return; } // Standard fallback path if (S.compareAndSet(this, TIMEOUT_MARKER, null)) { // FallbackSubscriber has defensive checks fallback.subscribe(new FallbackSubscriber<>(actual, this)); } // If CAS failed, cancel() was called - skip fallback } ``` ### 4. FallbackSubscriber with Defensive Checks Even with atomic subscription management, a misbehaving fallback publisher might ignore cancellation and continue emitting signals. FallbackSubscriber adds a final line of defense by checking cancellation state before forwarding each signal. ```java static final class FallbackSubscriber<T> implements CoreSubscriber<T> { Override public void onSubscribe(Subscription s) { // Operators.set() atomically: // 1. Checks if S is already cancelledSubscription() // 2. If so, cancels the incoming subscription and returns false // 3. Otherwise, sets S = s and returns true if (Operators.set(S, parent, s)) { s.request(Long.MAX_VALUE); } // If set() returned false, subscription was rejected } Override public void onNext(T t) { // Defensive check: drop signals if cancelled if (parent.s == Operators.cancelledSubscription()) { Operators.onNextDropped(t, actual.currentContext()); return; } actual.onNext(t); } Override public void onError(Throwable t) { if (parent.s == Operators.cancelledSubscription()) { Operators.onErrorDropped(t, actual.currentContext()); return; } actual.onError(t); } Override public void onComplete() { if (parent.s == Operators.cancelledSubscription()) { return; // Silently drop } actual.onComplete(); } } ``` **Why defensive checks in every method?** A misbehaving publisher might: - Ignore the `cancel()` call on its subscription - Continue emitting values after cancellation - Emit onError or onComplete after cancellation The defensive checks ensure no signals leak to downstream after cancellation. ### 5. Timer Injection for Testability **Before:** ```java // Timer hardcoded - impossible to test race conditions deterministically this.timeout = RpcResources.getHashedWheelTimer().newTimeout(this::doTimeout, delay, unit); ``` **After:** ```java private final Timer timer; // Public constructor uses default timer public MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback) { this(scheduler, delay, unit, fallback, RpcResources.getHashedWheelTimer()); } // Package-private constructor for testing VisibleForTesting MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback, Timer timer) { this.timer = Objects.requireNonNull(timer, "timer"); // ... } ``` ### 6. Input Validation **Before:** ```java public MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback) { this.scheduler = scheduler; // No validation this.delay = delay; // No validation this.unit = unit; // No validation this.fallback = fallback; } ``` **After:** ```java MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback, Timer timer) { this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); if (delay < 0) { throw new IllegalArgumentException("delay must be non-negative, was: " + delay); } this.delay = delay; this.unit = Objects.requireNonNull(unit, "unit"); this.fallback = fallback; // null allowed (means no fallback) this.timer = Objects.requireNonNull(timer, "timer"); } ``` ## Summary of Key Bugs Fixed 1. **Multiple signals possible**: Without atomic state, both source and timeout could emit signals. Now CAS ensures exactly one wins. 2. **Fallback ignores cancellation**: Method references bypassed all checks. Now FallbackSubscriber checks cancellation before every signal. 3. **Cancel after timeout lost**: Using `cancelledSubscription()` as timeout marker made cancel() a no-op. Now TIMEOUT_MARKER is distinct. 4. **Timer not cancelled**: No guarantee timer was cancelled on completion. Now `cancelTimer()` is called in all terminal paths. 5. **Untestable races**: Hardcoded timer made deterministic testing impossible. Now timer is injectable via package-private constructor. 6. **No input validation**: Invalid inputs (null scheduler, negative delay) were silently accepted. Now validated with clear error messages. Reviewed By: RayanRal, adolfojunior Differential Revision: D89081110 fbshipit-source-id: 55a711aef6c80f3ca6b8e6cfb572f29e2c115229Jeff Bahr · ba63c5af · 2026-02-09
- 2.9ETVAdd ALPN support to thrift java stack Summary: ## What this diff does Introduces a new server transport for Java Thrift — **`UnifiedServerTransport`** — that listens on a single TLS port and uses **ALPN (Application-Layer Protocol Negotiation)** to dynamically select the wire protocol per connection. Two protocols are advertised and supported: `"rs"` (RSocket) and `"thrift"` (THeader/Rocket). This enables in-place migration from Header → Rocket without touching listening ports, DNS, or connection routing. ## Why Today, a Java Thrift server is bound to exactly one wire protocol at process start (`LegacyServerTransportFactory` for Header, `RSocketServerTransportFactory` for Rocket). Migrating a service from Header to Rocket requires a port change, a load-balancer reconfiguration, or a full restart with a new transport. ALPN lets a single server speak both protocols simultaneously, so individual clients can be migrated one at a time without coordinating with the server-side rollout. This matches the C++ ThriftServer behavior, which has supported the same pattern for years. ## Architecture The diff adds five new classes under `com.facebook.thrift.transport.unified`: - **`UnifiedServerTransportFactory`** — `ServerTransportFactory` impl that constructs `UnifiedServerTransport` instances. Plugged into `RpcThriftServer` dispatch (see below). - **`UnifiedServerTransport`** — Reactor-Netty `TcpServer` bound to the configured address with TLS+ALPN. On `doOnConnection`, inspects `SslHandler.applicationProtocol()` and routes: - ALPN selected `"rs"` → adds RSocket length-field decoder, hands the connection to `RSocketServer.asConnectionAcceptor()` wrapped in `TcpDuplexConnection`. - ALPN selected `"thrift"` → adds `ThriftHeaderFrameLengthBasedDecoder` + `LengthFieldPrepender` and hands the connection to `ThriftConnectionAcceptor`. - ALPN absent or unrecognised → **falls back to HEADER** (matches C++ ThriftServer behavior; downgrades from "fail closed" to "fail safe" so legacy clients without ALPN still work). - **`ThriftConnectionAcceptor`** — Reactor-Netty connection handler that bridges Netty inbound bytes to `RpcServerHandler` via `ReactiveHeaderCodec`. Decodes `ByteBuf` → `ThriftFrame`, dispatches request kind (oneway / req-resp), encodes response back, manages timeouts and per-request reference counting. - **`ReactiveHeaderCodec`** — Stateless encoder/decoder for THeader frames, ported from `HeaderTransportCodec` with full parity to landed bug fixes (multi-transform decompression via `CompressionManager` in reverse order, persistent-only header-section parsing, `readString` bounds check, per-header error logging on parse failure). - **`UnifiedServerTransport$ThriftChannelInitializer`** — `ChannelPipelineConfigurer` that enforces `ThriftServerConfig.connectionLimit` and installs `MetricsChannelDuplexHandler` for accepted/rejected/dropped connection counters. Supporting changes: - **`ThriftTransportType`** — adds an `UNKNOWN` enum constant; `fromProtocol(null | unrecognised)` now returns `UNKNOWN` (instead of throwing) so the unified server can apply the fallback-to-HEADER policy uniformly. - **`RpcServerUtils`** — `getSslContext()` configures `ApplicationProtocolConfig` with `[RSOCKET, HEADER]` when `config.isEnableAlpn()`. Also adds `isSslCloseNotify(Throwable)` helper to filter benign TLS shutdown alerts from error logs. - **`RpcThriftServer`** — dispatch logic now routes to `UnifiedServerTransportFactory` first when `config.isEnableAlpn()`, then `RSocketServerTransportFactory` if `useRSocket`, then `LegacyServerTransportFactory` (the old default). - **Client side** (`ThriftClientInitializer`, `LegacyRpcClientFactory`) — propagates the remote `InetSocketAddress` into `SslContext.newHandler(...)` so the client SNI/ALPN handshake completes correctly. No client behavior change when ALPN is disabled. ## Migration strategy **Opt-in via config flag.** The new transport is activated only when `ThriftServerConfig.setEnableAlpn(true)` is called. Default behavior is unchanged — services that don't opt in continue to use `LegacyServerTransportFactory` or `RSocketServerTransportFactory` exactly as before. **Backwards compatible at the wire level.** A unified server with ALPN enabled accepts: - ALPN-aware clients negotiating `"thrift"` → routed to Header path (same wire format as existing Header servers). - ALPN-aware clients negotiating `"rs"` → routed to Rocket path (same wire format as existing Rocket servers). - Legacy clients that don't send an ALPN extension at all → falls back to Header (per the new no-ALPN handling). This means a Header-speaking legacy client can connect to an ALPN-enabled server without modification. **No client SDK changes required for adoption.** Any existing TLS-enabled Java client that does TLS handshake against an ALPN-enabled server will work — either via ALPN negotiation (when the client supports ALPN) or via the no-ALPN HEADER fallback. **Per-service rollout.** Services adopt the unified transport one at a time by setting `setEnableAlpn(true)` in their `ThriftServerConfig`. **Rollback.** A single-line config revert (`setEnableAlpn(false)`) returns a service to `LegacyServerTransportFactory`. No data-format compatibility concerns — the wire formats are unchanged on each ALPN path. Reviewed By: adolfojunior, RayanRal Differential Revision: D87745292 fbshipit-source-id: a5587ceef699ecf6893a9dcd97ab8fab19596af4Jeff Bahr · 972aa12a · 2026-04-30
- 2.8ETVAdd TLS pipeline for fast thrift server Summary: Adds a TLS pipeline for fast thrift server. Adds a TLS handler to the conneciton pipeline which owns the TLS pipeline. Adds PERMITTED mode for TLS. Reviewed By: robertroeser Differential Revision: D106108957 fbshipit-source-id: a2eed2115a251f60ea83bab304342d6802826d1eAnkit Kumar · 85b46823 · 2026-05-27
- 2.2ETVMove transport component for fast_thrift to thrift Summary: 1. Moves transport component for fast_thrift to thrift. 2. Fixes includes 3. Fixes depencies 4. Verifies tests run and pass 5. Verifies benchmarks Reviewed By: islamismailov Differential Revision: D99003116 fbshipit-source-id: 24d2dc2714ae2aa8e39efbd138dde68f057f664aAnkit Kumar · 45c2426f · 2026-04-02
- 2.0ETVRemove annotation lowering for cpp.type/cpp.template on typedefs Summary: Remove the typedef visitor in sema.cc that lowered structured cpp.Type annotations to unstructured cpp.type/cpp.template on the typedef's inner type (via add_annotations_to_node_type, which may wrap the inner type in an unnamed typedef). Instead, have generators check the typedef node directly for the structured cpp.Type annotation: - Update find_type/find_template in name_resolver.h to check structured annotations - Update get_underlying_type_name in name_resolver.cc to resolve cpp.Type on typedefs for using declarations - Update get_cpp_template in t_mstch_cpp2_generator.cc and t_mstch_py3_generator.cc - Update get_aliases_to_struct to check the typedef directly - Update get_cached_type_props call sites in py3 generator to walk from the original type Field lowering is kept (only the typedef visitor is removed), matching the scope of the prior java.swift.type change. Reviewed By: vitaut Differential Revision: D96346025 fbshipit-source-id: 924f365397778a351ef6a1cfd29cccb0a8bed94eShai Szulanski · 32412fc4 · 2026-03-19
- 2.0ETVImprove MonoTimeoutTransformer's reactive compliance and multithreaded safety Summary: # MonoTimeoutTransformer: Complete Rewrite for Reactive Streams Compliance ## Summary: This diff is a complete rewrite of MonoTimeoutTransformer to fix multiple race conditions, ensure Reactive Streams specification compliance, and add comprehensive test coverage. ## Why This Rewrite Was Necessary The original implementation had fundamental design flaws: 1. **No explicit state machine**: Used `isDisposed()` checks which are not atomic with respect to signal emission, allowing races. 2. **Incorrect fallback subscription**: Subscribed fallback directly to `actual::onNext, actual::onError, actual::onComplete` method references, bypassing cancellation checks entirely. 3. **Race conditions**: Multiple unhandled races between: - Source emission vs timeout firing (Race 1) - Cancel vs fallback subscription setup (Race 2) - Cancel after timeout but before scheduler execution (Race 3) 4. **No timer cleanup verification**: Timer cancellation on normal completion was not guaranteed. 5. **Untestable**: Timer was hardcoded, making deterministic race testing impossible. ## The Three Critical Race Conditions ### Race 1: Source vs Timeout (Signal Race) ``` Source Thread Timer Thread │ │ │── onNext() ──────────────────────────│── run(Timeout) ── │ [want to emit value] │ [want to emit error] │ │ └──────────── WHO WINS? ───────────────┘ ``` **Old behavior**: Both could win, potentially emitting both value and error. **New behavior**: Atomic CAS on STATE ensures exactly one winner. ### Race 2: Cancel vs Fallback Setup ``` Scheduler Thread Downstream Thread │ │ │─ fallback.subscribe(actual) ────────────────▶│ │ │── cancel() ── │ │ [too late?] ``` **Old behavior**: Fallback subscription ignored cancellation. **New behavior**: FallbackSubscriber uses Operators.set() to atomically check for cancellation before accepting the subscription. ### Race 3: Cancel After Timeout, Before Scheduler (TIMEOUT_MARKER Fix) ``` Timer Thread Downstream Thread Scheduler Thread │ │ │ │─ run(Timeout) ──────────▶│ │ │ [S → ???] │ │ │ [schedule(this)] │ │ │ │ │ │ │── cancel() ──────────────▶│ │ │ [Operators.terminate(S)]│ │ │ [did it work?] │ │ │ │ │ │ │─ run() ── │ │ │ [should skip?] ``` **Old behavior (if it existed)**: Would use `cancelledSubscription()` as marker in run(Timeout). But `cancel()` also uses `cancelledSubscription()`, so `Operators.terminate()` would see S as already cancelled and do nothing. The scheduler's `run()` would then proceed to emit signals to a cancelled subscriber. **New behavior**: Introduced `TIMEOUT_MARKER` - a distinct sentinel that marks "timeout fired, transitioning to fallback." This allows: 1. `run(Timeout)` sets S to TIMEOUT_MARKER (not cancelledSubscription) 2. If `cancel()` is called, `Operators.terminate()` successfully changes S from TIMEOUT_MARKER to cancelledSubscription() 3. `run()` uses CAS to check if S is still TIMEOUT_MARKER - if not, cancellation occurred and we skip signal emission ## Architecture Overview ### Old Architecture (Flawed) ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ MonoTimeoutTransformer (Old) │ │ │ │ ┌─────────────┐ ┌──────────────────────┐ ┌─────────────────────────┐ │ │ │ Source │───▶│ TimeoutSubscription │───▶│ Downstream │ │ │ │ Mono │ │ (just a holder) │ │ (CoreSubscriber) │ │ │ └─────────────┘ └──────────┬───────────┘ └─────────────────────────┘ │ │ │ │ │ │ creates │ │ ▼ │ │ ┌──────────────────────┐ │ │ │ SourceSubscriber │ ◀── extends BaseSubscriber │ │ │ (no state machine) │ (uses isDisposed()) │ │ └──────────────────────┘ │ │ │ │ Problems: │ │ - No atomic state management │ │ - Fallback subscribed via method references (no cancel check) │ │ - Race conditions between timeout/source/cancel │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### New Architecture (Correct) ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ │ MonoTimeoutTransformer (New) │ │ │ │ ┌─────────────┐ ┌───────────────────┐ ┌────────────────────────────┐ │ │ │ Source │─────▶│ TimeoutSubscriber │─────▶│ Downstream │ │ │ │ Mono │ │ (CoreSubscriber,│ │ (CoreSubscriber) │ │ │ └─────────────┘ │ Subscription, │ └────────────────────────────┘ │ │ │ TimerTask, │ │ │ │ Runnable) │ │ │ └────────┬──────────┘ │ │ │ │ │ ┌────────────────────┼────────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────────┐ │ │ │ STATE (atomic) │ │ S (atomic) │ │ FallbackSubscriber │ │ │ │ INIT=0 │ │ source sub │ │ (defensive cancel checks) │ │ │ │ VALUE_EMITTED=1│ │ TIMEOUT_MARKER │ │ - checks S before onNext │ │ │ │ TERMINATED=2 │ │ fallback sub │ │ - checks S before onError │ │ │ └─────────────────┘ │ CANCELLED │ │ - checks S before onComplete │ │ │ └─────────────────┘ └─────────────────────────────────┘ │ │ │ │ Key invariants: │ │ - STATE transitions are atomic (CAS) │ │ - Only one thread can win INIT → VALUE_EMITTED or INIT → TERMINATED │ │ - S tracks active subscription and cancellation state │ │ - TIMEOUT_MARKER enables Race 3 detection │ └─────────────────────────────────────────────────────────────────────────────────┘ ``` ## State Machine ``` ┌───────────────────────────────────────────────────┐ │ │ ▼ │ ┌──────────┐ │ │ INIT │◀─────────────────────────────────────────────┘ │ (0) │ (initial state) └────┬─────┘ │ ┌─────────┴─────────┬──────────────────┬──────────────────┐ │ │ │ │ │ onNext() │ timeout fires │ onComplete() │ cancel() │ [CAS succeeds] │ [CAS succeeds] │ [CAS succeeds] │ [getAndSet] ▼ │ │ │ ┌─────────────┐ │ │ │ │VALUE_EMITTED│ │ │ │ │ (1) │ │ │ │ └──────┬──────┘ │ │ │ │ │ │ │ │ onComplete() │ │ │ │ [CAS succeeds] │ │ │ ▼ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────┐ │ TERMINATED (2) │ │ (terminal state - no further state transitions allowed) │ └─────────────────────────────────────────────────────────────┘ ``` ## Key Implementation Changes ### 1. Atomic State Management (Race 1 Fix) The original code had no coordination between source signals and timeout. Both could proceed simultaneously, potentially delivering multiple terminal signals. **Before - Source emits value:** ```java Override protected void hookOnNext(T value) { timeout.cancel(); // Cancel timer, but might be too late actual.onNext(value); // Emit value unconditionally } ``` **Before - Timeout fires:** ```java private void doTimeout(Timeout timeout) { if (!isDisposed() && !timeout.isCancelled()) { // Check disposed doTimeout(); // But source might emit between check and here! } } private void doTimeoutException() { TimeoutException e = new TimeoutException("..."); scheduler.schedule(() -> actual.onError(e)); // Emit error } ``` **The Race:** If source emits while timeout fires: 1. `hookOnNext` calls `timeout.cancel()` - but timer callback already started 2. `doTimeout` checks `isDisposed()` → false (source hasn't finished yet) 3. Both `actual.onNext(value)` AND `actual.onError(e)` can execute 4. Downstream receives BOTH value and error → violates Reactive Streams spec **After - Both paths compete via atomic CAS:** ```java Override public void onNext(T t) { // CAS: Only ONE thread can transition INIT → VALUE_EMITTED if (STATE.compareAndSet(this, STATE_INIT, STATE_VALUE_EMITTED)) { cancelTimer(); actual.onNext(t); } else { // Lost the race - drop the value Operators.onNextDropped(t, actual.currentContext()); } } Override // TimerTask.run - called by timer thread public void run(Timeout timeout) { // CAS: Only ONE thread can transition INIT → TERMINATED if (STATE.compareAndSet(this, STATE_INIT, STATE_TERMINATED)) { // Won the race - cancel source and emit timeout Subscription current = S.getAndSet(this, TIMEOUT_MARKER); if (current != null) current.cancel(); scheduler.schedule(this); // Will emit error or subscribe fallback } // If CAS fails, source already won - do nothing } ``` **Why this works:** The CAS operation is atomic. Exactly one of source or timeout can successfully change STATE from INIT. The loser's CAS returns false, and they drop their signal. ### 2. Atomic Subscription Management (Race 2 & 3 Fix) The original code had no atomic tracking of subscription state. The new code uses an atomic `S` field to track the active subscription and detect cancellation. **Before:** ```java private static class TimeoutSubscription<T> implements Subscription { private SourceSubscriber<T> sourceSubscriber; // Not atomic! Override public void cancel() { if (sourceSubscriber != null) { sourceSubscriber.dispose(); // Just sets a boolean } } } ``` **Problem:** `dispose()` sets an internal boolean, but this isn't atomic with respect to subscription switching. If cancel() races with fallback subscription setup: - `dispose()` might set the boolean after fallback checks it - Or before fallback even exists **After:** ```java volatile Subscription s; static final AtomicReferenceFieldUpdater<TimeoutSubscriber, Subscription> S = ...; // TIMEOUT_MARKER: distinct sentinel for timeout-in-progress state static final Subscription TIMEOUT_MARKER = new Subscription() { ... }; Override // TimerTask.run public void run(Timeout timeout) { if (STATE.compareAndSet(this, STATE_INIT, STATE_TERMINATED)) { // Set S to TIMEOUT_MARKER (not cancelledSubscription!) Subscription current = S.getAndSet(this, TIMEOUT_MARKER); if (current != null) current.cancel(); scheduler.schedule(this); } } Override // Runnable.run - scheduler thread public void run() { // Race 3 detection: if cancel() was called, S is now cancelledSubscription if (s == Operators.cancelledSubscription()) { return; // Cancel won - don't emit anything } // CAS to detect late cancel: TIMEOUT_MARKER → null if (S.compareAndSet(this, TIMEOUT_MARKER, null)) { fallback.subscribe(new FallbackSubscriber<>(actual, this)); } // If CAS failed, cancel() changed S to cancelledSubscription - skip fallback } ``` **Why TIMEOUT_MARKER?** If we used `cancelledSubscription()` in run(Timeout): - `cancel()` calls `Operators.terminate(S, this)` which does: `S.getAndSet(this, cancelledSubscription())` - If S is already `cancelledSubscription()`, this is a no-op! - The scheduler's `run()` would have no way to detect the cancel With TIMEOUT_MARKER: - `cancel()` successfully changes S from TIMEOUT_MARKER → cancelledSubscription() - The scheduler's CAS from TIMEOUT_MARKER → null fails - Cancel is detected, fallback is skipped ### 3. Proper Fallback Handling (Race 2 Fix) The original code subscribed to fallback using method references, which bypassed all cancellation checks. **Before:** ```java private void doTimeoutFallback() { scheduler.schedule(() -> { try { // WRONG: Method references go directly to downstream! // If cancel() is called, these lambdas still execute! fallback.subscribe(actual::onNext, actual::onError, actual::onComplete); } catch (Throwable t) { actual.onError(t); } }); } ``` **Problem:** `actual::onNext` is just a method reference - it has no cancellation check. Even after `cancel()`, fallback signals go directly to downstream. **After:** ```java Override public void run() { // Check cancellation before any signal emission if (s == Operators.cancelledSubscription()) { return; } // Standard fallback path if (S.compareAndSet(this, TIMEOUT_MARKER, null)) { // FallbackSubscriber has defensive checks fallback.subscribe(new FallbackSubscriber<>(actual, this)); } // If CAS failed, cancel() was called - skip fallback } ``` ### 4. FallbackSubscriber with Defensive Checks Even with atomic subscription management, a misbehaving fallback publisher might ignore cancellation and continue emitting signals. FallbackSubscriber adds a final line of defense by checking cancellation state before forwarding each signal. ```java static final class FallbackSubscriber<T> implements CoreSubscriber<T> { Override public void onSubscribe(Subscription s) { // Operators.set() atomically: // 1. Checks if S is already cancelledSubscription() // 2. If so, cancels the incoming subscription and returns false // 3. Otherwise, sets S = s and returns true if (Operators.set(S, parent, s)) { s.request(Long.MAX_VALUE); } // If set() returned false, subscription was rejected } Override public void onNext(T t) { // Defensive check: drop signals if cancelled if (parent.s == Operators.cancelledSubscription()) { Operators.onNextDropped(t, actual.currentContext()); return; } actual.onNext(t); } Override public void onError(Throwable t) { if (parent.s == Operators.cancelledSubscription()) { Operators.onErrorDropped(t, actual.currentContext()); return; } actual.onError(t); } Override public void onComplete() { if (parent.s == Operators.cancelledSubscription()) { return; // Silently drop } actual.onComplete(); } } ``` **Why defensive checks in every method?** A misbehaving publisher might: - Ignore the `cancel()` call on its subscription - Continue emitting values after cancellation - Emit onError or onComplete after cancellation The defensive checks ensure no signals leak to downstream after cancellation. ### 5. Timer Injection for Testability **Before:** ```java // Timer hardcoded - impossible to test race conditions deterministically this.timeout = RpcResources.getHashedWheelTimer().newTimeout(this::doTimeout, delay, unit); ``` **After:** ```java private final Timer timer; // Public constructor uses default timer public MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback) { this(scheduler, delay, unit, fallback, RpcResources.getHashedWheelTimer()); } // Package-private constructor for testing VisibleForTesting MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback, Timer timer) { this.timer = Objects.requireNonNull(timer, "timer"); // ... } ``` ### 6. Input Validation **Before:** ```java public MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback) { this.scheduler = scheduler; // No validation this.delay = delay; // No validation this.unit = unit; // No validation this.fallback = fallback; } ``` **After:** ```java MonoTimeoutTransformer(Scheduler scheduler, long delay, TimeUnit unit, Mono<T> fallback, Timer timer) { this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); if (delay < 0) { throw new IllegalArgumentException("delay must be non-negative, was: " + delay); } this.delay = delay; this.unit = Objects.requireNonNull(unit, "unit"); this.fallback = fallback; // null allowed (means no fallback) this.timer = Objects.requireNonNull(timer, "timer"); } ``` ## Summary of Key Bugs Fixed 1. **Multiple signals possible**: Without atomic state, both source and timeout could emit signals. Now CAS ensures exactly one wins. 2. **Fallback ignores cancellation**: Method references bypassed all checks. Now FallbackSubscriber checks cancellation before every signal. 3. **Cancel after timeout lost**: Using `cancelledSubscription()` as timeout marker made cancel() a no-op. Now TIMEOUT_MARKER is distinct. 4. **Timer not cancelled**: No guarantee timer was cancelled on completion. Now `cancelTimer()` is called in all terminal paths. 5. **Untestable races**: Hardcoded timer made deterministic testing impossible. Now timer is injectable via package-private constructor. 6. **No input validation**: Invalid inputs (null scheduler, negative delay) were silently accepted. Now validated with clear error messages. Reviewed By: robertroeser Differential Revision: D95107830 fbshipit-source-id: 3b869f00824a14d14ae394706190ec2896736789Jeff Bahr · ff73b836 · 2026-03-05
- 2.0ETVrefacrored rsocket connection Summary: Initial refactor of RocketServerConnection. Adds a connection adapter class that acts as a bridge between the IRocketServerConnection interface and the concrete implementation, breaking cyclic dependencies. Moves frame handling to use specialized frame handlers and incoming frame processing. The incoming frame handler is a type-safe handler for incoming frames. After parsing the frame type, it delegates to appropriate specialized handlers (SetupFrameAcceptor, RequestResponseHandler, etc.). Adds incoming and outgoing frame batching that is tied to the EventBase instead of individual connections. This improves performance by batching operations across multiple connections sharing the same event loop, using intrusive lists for efficient per-EventBase frame processing queues. (InstrusliveList version is another diff) Refactors write operations into modular components (ConnectionWriterCallback, ConnectionBufferCallback, WriteBatcher) for better separation of concerns and testability. Reviewed By: cevans87 Differential Revision: D78363607 fbshipit-source-id: 00591f55c0d1cc63f591cd5e0161dd80ae0e1d51Robert Roeser · 02ab0202 · 2025-10-16
- 2.0ETVMove tests and benchmarks for fast_thrift to thrift Summary: 1. Moves tests and benchmarks for fast_thrift to thrift. 2. Fixes includes 3. Fixes depencies 4. Verifies tests run and pass 5. Verifies benchmarks Reviewed By: islamismailov Differential Revision: D99007424 fbshipit-source-id: a9ab6eae15b5fd619bdaf494b31b6320cf24c440Ankit Kumar · 97290287 · 2026-04-02
- 1.9ETVDynamicValue 1/n: skeleton Summary: DynamicValue is the type-safe successor to protocol value. It uses TypeSystem to tag values with their runtime schemas and provide checked access to and operations on the contents. The guiding principle is to provide value semantics. DynamicRef is introduced to provide mutable views into containers/structs in later diffs. Reviewed By: praihan Differential Revision: D85101166 fbshipit-source-id: 57e39f6d4cd23cbcef77e82cad97318f5af2546dShai Szulanski · 98d0d8c8 · 2025-12-01
- 1.9ETVFix state races in FusableReconnectingRpcClientMono Summary: Replaces the two separate AtomicReferenceFieldUpdaters (state + rpcClient) with a single composite ConnectionState reference and makes all state transitions CAS-guarded. Also fixes several additional concurrency and resource lifecycle issues. Bug 1: Torn reads between state and rpcClient. Fix: Combined into a single immutable ConnectionState(phase, client) object. One AtomicReferenceFieldUpdater, one CAS per transition, no torn reads. Bug 2: handleIncomingRpcClient/handleConnectionError used unconditional set(). A stale connection attempt's success could clobber a healthy connection, or a stale failure could tear down a good replacement. Fix: Both callbacks CAS against their specific attempt's ConnectionState identity. Stale results are discarded (client disposed on stale success). Bug 3: onClose callback had a TOCTOU race — read snapshot, then unconditional set to DISCONNECTED. Another thread could advance state between read and write. Fix: trySetStateDisconnected uses CAS on the exact observed snapshot. The onClose callback uses a CAS-loop: read → check identity → CAS to DISCONNECTED. Bug 4: CONNECTING state carried the old client reference. Old client's onClose identity guard matched during CONNECTING, triggering spurious duplicate connects. Fix: CONNECTING uses client=null. Bug 5: Cancelled subscribers leaked during outages. The queue was only drained during CONNECTED. During prolonged DISCONNECTED/CONNECTING cycles, cancelled InnerSubscription objects accumulated unboundedly. Fix: drain() calls pruneCancelledSubscribers() when not CONNECTED. Bug 6: Backoff was applied as a timeout (MonoTimeoutTransformer) instead of a delay. This made backoff act as an increasingly strict timeout on the connection attempt — cancelling slow-but-valid TCP handshakes. On instant failures (connection refused), there was zero delay, causing a CPU-pegging spin-loop. Fix: Backoff applied as delaySubscription() before the attempt. A separate fixed CONNECT_TIMEOUT (10s) limits individual connection attempts. Bug 7: drainWithRpcClient emitted to all queued subscribers without rechecking isDisposed(). If a connection died mid-drain, remaining subscribers received a dead client instead of waiting for reconnection. Fix: Check isDisposed() at the top of the drain loop. Break if disposed. Bug 8: ClassCastException in trySetStateDisconnected. Hard cast to Scannable on onClose() Mono would throw if the publisher didn't implement Scannable, wedging the state machine permanently in CONNECTED with a dead client. Fix: Use Scannable.from() which safely wraps any publisher. Bug 9: Duplicate subscriber queue entries. request(n) used a plain volatile boolean for the enqueue-once guard. Concurrent request() calls could both observe enqueued=false and enqueue the same InnerSubscription twice. Fix: AtomicIntegerFieldUpdater CAS(0, 1) ensures exactly one enqueue. Reviewed By: prakashgayasen, cnli87 Differential Revision: D100047014 fbshipit-source-id: a076b4cad0294dc6755d417ea8bba1a8c93b4089Jeff Bahr · 9fe1d18c · 2026-04-14
- 1.9ETVRemove deprecated AST node type methods is_container/is_list/is_map/is_set Summary: - Remove AST type node methods `is_container`, `is_list`, `is_map`, `is_set` - Replace usages with `is<t_container>`, `is<t_list>`, `is<t_map>`, `is<t_set>` respectively #buildall Reviewed By: thedavekwon, iahs Differential Revision: D77047316 fbshipit-source-id: 0d744c7400590543f2d0d3c31587f5a10829d089Harsh Chokshi · be87fa36 · 2025-07-02
- 1.8ETVTypeSystemDigest Summary: Adds a canonical, deterministic (SHA-256) digest for `TypeSystem` (`type_system.thrift`) in C++ (for now). This enables efficient equivalence checking across runtime and serialized representations, useful for cache invalidation, version compatibility checks, and deduplication. The key goal is to have a deterministic hash, independent of serialization, language & platform behavior: - Ordering is normalized (types sorted by URI, fields by ID, etc) so structurally equivalent type systems always produce identical hashes regardless of definition order. - Runtime types (`TypeSystem`, `TypeRef`, `TypeId`) and their serializable counterparts (`SerializableTypeSystem`, `SerializableTypeDefinition`, `TypeIdUnion`) produce identical digests for *equivalent* types. - Includes a version byte prefix for future algorithm evolution - So we can invalidate in case of major changes/bugs/etc Reviewed By: praihan Differential Revision: D93045913 fbshipit-source-id: b81535f5c197b2b4d20f19cb1dd8562246a767d2Sam De Roeck · 2248b125 · 2026-03-06
- 1.8ETVAdds TLS support for Fast Thrift Server Summary: Adds TLS support for fast thrift server, which defaults to Required. Reviewed By: robertroeser Differential Revision: D103565194 fbshipit-source-id: 2375933a9ae9d7b1297af5794a321e54638d86dfAnkit Kumar · 636d85fe · 2026-05-11
- 1.7ETVTLS hot-reload via folly::Observer + mTLS/ticket-cipher parity Summary: Promote the `(fizzContext, thriftParams, handshakeTimeout)` triple into a single immutable `security::TLSParams` aggregate and distribute it pull-based through a `folly::observer::SimpleObservable` owned by `ConnectionManager`. `TLSDetectionHandler` and `FizzHandshakeHandler` store an `Observer<shared_ptr<const TLSParams>>` and snapshot it per accepted connection, so a single `setValue` on the observable replaces the previous per-EVB `setTLSParams` fan-out. In-flight handshakes still survive the swap via the inner `shared_ptr`s they captured at `start()`. `FizzServerCertConfig` gains the knobs needed for parity with classic `apache::thrift::ThriftServer` deployments: mTLS via `caPath` or a caller-supplied `fizz::CertificateVerifier`, a caller-supplied `fizz::Factory`, ALPN policing (`AlpnMode`), AEGIS cipher preference, and `TicketCipherSeeds` (old/current/new rotation, with `IdentityOnly` or `X509` cipher kind). `FizzServerContextBuilder` wires all of those into the produced `fizz::server::FizzServerContext`. `buildTLSParams` is the single entry point and throws synchronously on invalid configuration, so a bad reload leaves the running server untouched. `FastThriftServer::reloadTLSConfig(FizzServerCertConfig)` is the public hot-reload entry point: builds new `TLSParams` outside the lifecycle lock, then publishes via `ConnectionManager::setTLSParams`. Intended for cert / ticket-key rotation driven by an embedder-owned watcher. Reviewed By: robertroeser Differential Revision: D106117173 fbshipit-source-id: db3a42ac37d4e4202397e84e7c258793b4434abdAnkit Kumar · c777cfd2 · 2026-05-27
- 1.7ETVReplace t_field::get_type usages with type().get_type() Summary: Replace usages of deprecated method `t_field::get_type`. `t_field::get_type` is implemented as `type().get_type()`, where `type()` returns `t_type_ref` and `t_type_ref::get_type()` returns `t_type*` (the `type_` pointer in `t_type_ref`). Replace usages with the implementation as part of AST deprecated method cleanup, to enable removal of `t_field::get_type`. Reviewed By: iahs Differential Revision: D85359546 fbshipit-source-id: 363405913e3c3e7da44220459ed3c9362192aed4Harsh Chokshi · 6e77d16a · 2025-10-23
- 1.7ETVApply clang-tidy to thrift/compiler Reviewed By: vitaut Differential Revision: D84539364 fbshipit-source-id: dfc677c29be2d346a79ac59ebb0c7d597ec5c6e4Shai Szulanski · 613a33e1 · 2025-10-14
- 1.6ETVthrift metrics initial support Summary: Defines concepts that you can use to support metrics in fast thrift. Should allow different implementations of metrics without needs a concrete implementation so we can plug in meta specific things in etc Differential Revision: D102058497 fbshipit-source-id: 2118f8655c6d1e0a4e750114366133d8f0333ca2Robert Roeser · 2d090889 · 2026-05-05
- 1.6ETVFix client side error handling Summary: Fixes fast thrift client side error handing to handle overloaded, and other exception types, and have full parity with current Rocket client. Reviewed By: robertroeser Differential Revision: D101433516 fbshipit-source-id: 9b5578e2144f96d68f4257258032287c1d4044d2Ankit Kumar · bf367534 · 2026-04-24