Feature: Expose thread-safe C++ API for WebSocket binary sends from background threads

Đang mở
#436 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
30/100
Loại issue
Tính năng
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Đình trệ
Công nghệ
cpp, r
Lĩnh vực
api, backend

Hướng nghiên cứu

Read src/httpuv.cpp and the existing sendWSMessage path, then compare later_api.h’s public-header approach. Define the new inst/include/httpuv_api.h surface around the proposed thread-safe binary-send entry point and verify its connection-lifetime, queue, and copy semantics; done means downstream C++ code can use the documented API without the R main-thread assertion.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Summary

We'd like to send WebSocket binary messages from a C++ background thread (via an Rcpp package) without requiring the main R thread to be idle. Currently the only path is sendWSMessage(), which begins with ASSERT_MAIN_THREAD() and therefore cannot be called from any thread other than R's main thread. This makes it impossible to stream high-frequency data to a browser without being blocked by R's event loop.


Motivation

Consider a real-time multichannel signal streaming use case — for example, 100 channels at 2000 Hz displayed in a Shiny app. The current data pipeline looks like this:

[acquisition / callr bg process]
       ↓  (shared memory)
[Shiny main process]
       ↓  later::later() fires → session$sendBinaryMessage()
       ↓  sendWSMessage() → ASSERT_MAIN_THREAD() → background_queue->push()
       ↓  I/O thread writes to socket
[browser]

The actual socket write already happens off the main R thread via background_queue. The ASSERT_MAIN_THREAD() guard in the Rcpp-exported sendWSMessage only protects the thin dispatch layer — the byte copy and queue push — which are microsecond-level operations. However, the requirement that this dispatch happen on the main R thread means that any blocking R operation (a reactive computation, a database query, a Sys.sleep) will stall the stream, since later::later() callbacks only fire when the R call stack is empty or run_now() is called explicitly.

If we could push directly to background_queue from a C++ thread in a downstream Rcpp package — bypassing the main-thread assertion — the send path would be fully decoupled from R's scheduler:

[C++ background thread in downstream Rcpp package]
       ↓  reads from lock-free ring buffer (e.g. bigmemory)
       ↓  encodes Int16 raw bytes
       ↓  pushes directly to httpuv's background_queue
       ↓  I/O thread writes to socket
[browser]

R blocking would become completely irrelevant to the stream.


What already exists
  • background_queue is a file-scoped static CallbackQueue* in src/httpuv.cpp — not accessible from outside the translation unit.
  • httpuv exposes no public C++ headers in inst/include/.
  • The WebSocketConnection class and the queue push pattern are entirely internal.

Prior art: The later package solves an analogous problem by exposing its C++ event loop API via inst/include/later_api.h, allowing downstream Rcpp packages to schedule callbacks onto the correct thread without going through R. httpuv could follow the same pattern.


Proposed API

A minimal addition to a new inst/include/httpuv_api.h:

namespace httpuv {

// Thread-safe. May be called from any thread.
// Schedules a binary WebSocket send on the I/O background thread.
// conn_xptr: the SEXP XPtr<shared_ptr<WebSocketConnection>> from R
// data:      pointer to raw bytes (copied internally before return)
// len:       number of bytes
// Returns false if the connection is closed or the queue is unavailable.
bool send_binary_threadsafe(SEXP conn_xptr, const char* data, size_t len);

} // namespace httpuv

Internally this would do exactly what sendWSMessage does after the assert — copy bytes into a heap-allocated vector<char> and push the callback and deleter onto background_queue — but without the ASSERT_MAIN_THREAD() guard, since background_queue->push() is already designed for cross-thread use (it signals a uv_async_t).


Safety considerations
  • background_queue->push() is already thread-safe by design — it uses uv_async_send internally.
  • The byte copy (new std::vector<char>(data, data + len)) is safe on any thread as it only allocates from the heap.
  • The conn_xptr would be captured as a shared_ptr copy (not a raw pointer), keeping the connection alive for the duration of the callback.
  • The ASSERT_MAIN_THREAD() guard would remain on the existing sendWSMessage R-level export. This new function is purely additive.

Alternatives considered

I have the following alternatives, each has its own problems. I figured out the most straightforward way is to let httpuv expose its C++ interface directly.

Alternative Why it falls short
Second httpuv server on a separate port Requires the client to open a second WebSocket connection — not always possible behind single-port proxies or firewalls
later::later() loop Still requires R to be idle; any blocking R operation stalls the stream

Minimum viable ask

Even just making background_queue accessible as an opaque handle through a thin C API — similar to how later exposes later::later_api::Later — would be sufficient. A full public object model is not needed: just the ability to push a pre-encoded binary payload onto the I/O queue from a C++ thread without touching R.

Ngôn ngữ chính
C
Star
252
Fork
87
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của rstudio/httpuv

Tất cả issue của rstudio/httpuv

Issue tương tự

Thêm issue về C

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.