- The package installs where gRPC C++ cannot be linked, as a stub whose
functions error; the new
grpc_available()reports which build is installed, and tests and examples skip on a stub. CRAN's macOS builders have no gRPC, and its libc++ clang builders have only a libstdc++ gRPC, which linked into a shared object that then failed to load.configurenow links a test program with R's own C++ compiler instead of trusting pkg-config alone.RGRPC_REQUIRE_GRPC=trueturns the stub fallback into an install error; CI sets it on the legs that have gRPC and adds a leg without it. - The
.Callroutine table moved tosrc/routines.h, shared by the real build and the stub. - gRPC is initialized once, when the DLL loads, and shut down at unload
or R exit, instead of coming up and down with every client and server.
Each start ran abseil's debug deadlock bookkeeping, whose
frame-pointer stack walk read uninitialized stack through the
package's frames on builds without frame pointers: the valgrind
reports on CRAN's Fedora machine. Fork behavior is unchanged
(
tools/fork-probe.sh). configurecompiles its gRPC link test with R's PIC flags, so a-piein LDFLAGS no longer fails it on hardened toolchains.configurechecks for pkg-config withpkg-config --versionrather thancommand -v, which R-devel's check flags as a possible bashism.- On macOS the package links CoreFoundation, which a static abseil (as in CRAN's macOS recipes) needs but does not list in its pkg-config files.
- SystemRequirements names the Fedora (
grpc-cpp,grpc-devel,protobuf-devel) and Homebrew packages as well as the Debian ones. - The TLS tests set certificate extensions explicitly, so they pass with OpenSSL 1.1, OpenSSL 3, and LibreSSL, and stop cleanly if the certificates cannot be generated.
configuredrops warning options from pkg-config's flags and passes the include directories as-isystem. abseil's .pc files carry its own-Wno-...options, which R CMD check reported as non-portable flags, and gRPC 1.83's headers raise deprecation warnings against newer abseil releases (Homebrew), which it reported as the package's.- CI installs
checkbashismson macOS, targets the runner's macOS when linking Homebrew's gRPC, and fails on check WARNINGs.
- First CRAN release, resubmitted after the review of 0.1.0. DESCRIPTION links the
gRPC project and its C++ API; every exported function documents its
return value, including the seven called for their side effects; and
the examples run instead of sitting in
\dontrun{}: each one drives a client and a server in the same process over the loopback interface.grpc_tls()keeps a\dontrun{}example because it needs certificate files.
- Renamed from
grpctorgrpcbefore the first release: the bare name is a search token owned by the upstream project, and a unique name keeps this package's docs and issues findable. Exported functions keep theirgrpc_*names. - First CRAN submission (returned for documentation fixes; see 0.1.1):
an asynchronous gRPC client and server runtime on
the generic C++ API, with unary and streaming calls, TLS/mTLS,
deadlines, keepalive, cancellation, and event-loop integration via
grpc_fd(). Runs on Linux, Windows (Rtools >= 4.3), and macOS. The 0.0.1.x entries below record the development history.
- The package builds on Windows and macOS. The completion wake-up was
the one platform-bound piece of the runtime: eventfd is Linux-only,
and
src/wake.hreplaces it with the same contract everywhere -- a self-pipe on Unix, a loopback socket pair on Windows, readable exactly while events are queued. Windows links the gRPC that Rtools >= 4.3 bundles, through a staticMakevars.win, andOS_type: unixleft DESCRIPTION. CI gains a macOS leg building against Homebrew's gRPC.
- Depends dropped from R (>= 4.4.0) to R (>= 4.3.0), the version noble ships. (This heading was added retroactively; the bump rode with the change in #23 without a NEWS entry.)
grpc_await()now works on a server"grpc_request"too, closing the asymmetry 0.0.1.11 left behind: the docs said "one queue serves the whole client or server" while the remedy shipped for the client only, so a server driving concurrent streaming calls still hand-rolled theiddispatch that had already gone wrong five times downstream. A handler can drain one client-streaming call withgrpc_await(req, timeout_ms)and see nothing belonging to any other.grpc_await()became an S3 generic to accommodate that third class. Call sites are unaffected: the signature is unchanged and the existing"grpc_call"and"grpc_stream"behaviour is identical.- On a server request the awaited events are the ones that follow the
request itself (
"stream_msg","client_done","stream_writable","cancelled"), since the request arrives fromgrpc_poll()in the first place. A server call has no terminal event of its own -- it ends when the handler ends it -- so"client_done"is what a client-streaming handler loops to.
- New
grpc_await(x, timeout_ms): waits for events belonging to one call or stream. Other calls' events are stepped over and left queued in arrival order, and the wait ends only when a matching event arrives. This is the demultiplexing every other gRPC binding does for you -- Python hands back a per-call future or response iterator and never exposes a completion queue -- and its absence is what made the attribution mistakes in 0.0.1.10 possible in the first place. Use it for sequential code; usegrpc_poll()and dispatch onidwhen calls really are concurrent, since awaiting one call means not looking at the others. - The filter lives in C over the existing ready deque rather than in an R-side buffer, so there is no second queue to keep in sync and a filtered read cannot lose or reorder another call's events. The wait is filter-aware: it cannot lean on the eventfd, because another call's queued events keep the descriptor readable and the wait would spin instead of blocking.
- An expired
grpc_await()leaves the call exactly as it was: await it again to keep waiting. That resumability is what keeps a requiredtimeout_msfrom being the empty-batch trap in new clothing, so it is now stated rather than implied, along with which clock is which (deadline_msbounds the RPC,timeout_msbounds one wait). Examples check the batch before indexing it: on a slow peergrpc_await(call, timeout_ms = 1000)[[1]]raisessubscript out of bounds, which reads like a caller bug rather than the timeout it is. - The documentation stopped teaching the bug. The README's round-trip
and typed-call examples, and
inst/examples/cri-list-pods.R, all indexed a poll batch asevs[[1]]or hand-rolled adrain()helper that returned the first event of whatever arrived. Correct only while nothing else is in flight, which is exactly how a caller infers the wrong model.
- Fixed spurious wake-ups from
grpc_poll()(#12). The eventfd carries one signal per event, but the counter was drained outside the mutex guarding the ready deque, so every signal posted between the drain and the batch below stayed counted even though the batch took those events. The leftover count was then spent as instant, empty polls: a caller that reads "no events" as "this call never started" abandons a live stream, which showed up as sequential server-streaming calls on a shared client failing 1-7 times in 30. The drain and a re-arm now happen under that mutex, so the descriptor is readable exactly while events are queued. Affects clients and servers alike, andgrpc_fd()integrations most of all, since a partial batch used to leave the descriptor unreadable with events still pending. - Documented what an empty
grpc_poll()result means: the wait expired, not that the call is over. - Documented event attribution: one queue serves the whole client, so
a batch can mix events from every call in flight, in completion
order, and callers must dispatch on
id. Takingevents[[1]]as the answer to a unary call and accumulating every"stream_msg"into one stream's payload are the same assumption -- one queue per call -- and it does not hold. Abandoning a stream unread does not stop it, and its queued messages go on surfacing alongside later calls;grpc_cancel()bounds how many more are produced but cannot recall events already queued. Verified against the runtime rather than assumed: over 40 rounds of a deliberately abandoned stream followed by a second stream on the same client, no event'sidever disagreed with its payload, while an accumulator that ignoredidgot the wrong byte count 36 times out of 40.
- Fixed a use-after-free race found by the first genuinely
instrumented ThreadSanitizer run:
grpc_r_call_start()andgrpc_r_stream_start()read the call id after releasing the mutex, and a fast completion can free the call state on the drain thread in that window. The id is now captured under the lock. tools/sanitize.shis fail-closed about its own preconditions, after three stacked silent failures let earlier sanitizer passes run the plain build: libraries are injected viaR_LIBSand asserted withfind.package()(littler's-Lsilently does not prepend), sanitizer flags go throughCXX17FLAGSand instrumentation is proven via__asan/__tsansymbols in the built.so, installs use--preclean, an exit trap removes flagged objects even on failure, non-data-race TSan warnings are fatal, and the report classifier self-tests against synthetic logs at every run.
- Keepalive:
keepalive_ms/keepalive_timeout_msongrpc_client()andgrpc_server(), plusmin_ping_interval_ms(server tolerance for client pings; gRPC's default is 5 minutes and kills faster clients). Enabling keepalive also lifts gRPC's ping policing so pings flow on quiet connections. - Abortive close:
grpc_finish(drain = FALSE)discards queued stream writes and prioritizes the terminal status (fencing);grpc_cancel()now also works on server requests as the hard escalation past a peer that has stopped reading.
- Fixed a completion-queue shutdown race that aborted the process when
closing a client (or server) with stream operations in flight: the
drain thread could post a follow-on op after
cq.Shutdown(). Ashuttingflag now suppresses all drain-thread op posts during close, on both client and server. - Forced-error cleanup tests: post-close operations error cleanly, finalizers with work in flight, create/destroy churn.
- Verified under valgrind (0 errors, 0 definite/indirect leaks; possibly-lost noise from gRPC/absl thread-locals and R remains), AddressSanitizer (clean), and ThreadSanitizer (all reports trace to the uninstrumented system libgrpc boundary; none in package code).
- Reference node image under
docker/: two-stage build onrocker/r2u:noble, runtime carries onlylibgrpc++1.51t64andr-cran-rprotobuf; built and smoke-tested. The base image release must match the build host's — the binary is tied to the distro ABI. - configure and
SystemRequirementsnow namelibprotobuf-devalongsidelibgrpc++-dev(not pulled automatically under--no-install-recommends).
- Operational surface:
grpc_tls()builds PEM credentials for both sides (TLS server, CA-pinned client, mTLS withrequire_client_cert,target_name_overridefor testing); server request events carry the transportpeerand, under mTLS, the verifiedpeer_identity;grpc_state()observes channel connectivity;GRPC_TRACE/GRPC_VERBOSITYdocumented. - Health checking (
grpc.health.v1) and server reflection (grpc.reflection.v1) protos vendored and proven through the generic path — unary Check and bidi ServerReflectionInfo — with no special machinery.
- Streaming:
grpc_stream()opens client-, server-, and bidirectional streams (typed or raw);grpc_send()(bounded write queue withstream_writablebackpressure events),grpc_writes_done(), automatic bounded inbound buffering with HTTP/2 backpressure, and a terminalstream_statusevent. Server side:grpc_read()pulls inbound stream messages,grpc_send()queues outbound ones,grpc_finish()ends the stream after writes drain.grpc_cancel()is now a generic over calls and streams; client events carrykind. - Live CRI streaming smoke: subscribe, hold, and cancel
GetContainerEventsagainst containerd.
- Real-world proof: CRI client against live containerd (v2.2.3) over a
unix-domain socket —
Version,ListPodSandbox,ListImages, all through the typed layer. CRI v1 schema vendored as a self-contained import root underinst/proto/cri; live tests gate on socket access (GRPC_R_CRI_SOCKET);inst/examples/cri-list-pods.Rfor humans. - PLAN.md: Viento experiment reframed as control-plane candidacy.
- RProtoBuf integration:
grpc_service()/grpc_method()resolve services and methods from the runtime descriptor pool (via the FileDescriptorProto, sidestepping rprotobuf#116);grpc_call()accepts agrpc_methodplus aMessage, validates the request type, and auto-decodes OK responses asresponse_message;grpc_reply()accepts aMessage;grpc_decode()decodes payload bytes. RProtoBuf remains a Suggests.
- Generic asynchronous server on
AsyncGenericService:grpc_server()accepts any method dynamically;grpc_reply()answers with a payload or a typed status plus trailing metadata. Bounded accept window andmax_activeapply backpressure. grpc_poll(),grpc_fd(),grpc_pending(), andgrpc_close()are now S3 generics over clients and servers. Peer cancellation and deadline expiry surface as"cancelled"events; late replies are refused rather than crashing.- Full in-process round trips (TCP and unix-domain sockets) covered in tests.
- Generic asynchronous unary client on
GenericStub:grpc_client(),grpc_call(),grpc_poll(),grpc_cancel(),grpc_pending(),grpc_close(), with deadlines, metadata, and typed status codes. - Event-loop wakeup via eventfd, exposed through
grpc_fd()forlater::later_fd()-style integration; polling is the fallback, not the primitive.
- Initial skeleton: system-linked build against the distro gRPC C++
library via
pkg-config, spike shim proving safe create/destroy of channels, completion queues, and generic servers.