Replies: 7 comments 15 replies
-
A few thoughts:
|
Beta Was this translation helpful? Give feedback.
-
A few more thoughts:
Come to think of it, do we even need to present the endpoint abstraction to our users? AFAIU they just want to schedule execution of a callback in the TX thread from a user thread. Let's give them just that: /**
* Initializes the remote call subsystem.
*
* Must be called by each user thread that needs to invoke callbacks
* in the tx thread. Under the hood, it creates a pipe to the "tx"
* endpoint.
*
* [to be called in user threads]
*/
void
tnt_remote_init(void);
/**
* Frees the remote call subsystem.
*
* Must be called at exit by each each user thread that has initialized
* the remote call subsystem. Under the hood, it destroys the pipe to
* the "tx" endpoint created with tnt_remote_init().
*
* [to be called in user threads]
*/
void
tnt_remote_free(void);
/**
* Sets the max number of messages to buffer in the user thread before
* flushing them to the tx thread.
*
* The default value is INT_MAX (flush manually).
*
* See also tnt_remote_flush().
*
* [to be called in user threads]
*/
void
tnt_remote_set_backlog(int backlog);
/**
* Schedules a callback to be invoked in the tx thread.
*
* The callback will be executed in a fiber taken from the tx fiber pool.
* (This pool is also used for processing IPROTO requests.)
*
* Note the callback isn't necessarily pushed to the tx thread immediately.
* Instead it may be queued locally in the user thread. To flush the local
* queue, use tnt_remote_flush().
*
* See also tnt_remote_set_backlog().
*
* [to be called in user threads]
*/
void
tnt_remote_call(void (*cb)(void *), void *arg);
/**
* Flushes all pending messages queued with tnt_remote_call() to the tx thread.
*
* [to be called in user threads]
*/
void
tnt_remote_flush(void); |
Beta Was this translation helpful? Give feedback.
-
This section looks like a leftover from the previous document version because there's no mentions of
If we used one endpoint for both system and user threads, this would be a problem. However, since we agreed to introduce a new endpoint for user messages, I think we don't need to bother making this endpoint work with system threads.
I wouldn't mix user and system pipes in one class, even if it entailed some code duplication, because they are quite different. Probably, it'd be better to introduce a new thread-local C++ singleton for initializing user pipes on demand. |
Beta Was this translation helpful? Give feedback.
-
I want @mkokryashkin have a look as the "other reviewers" mentioned. |
Beta Was this translation helpful? Give feedback.
-
I’ve reviewed the RFC and the discussion, and I believe the most valuable insight I can provide is from the perspective of someone integrating with the Tarantool C API via a module.
What happens if a user initiates a transaction and submits it incrementally, call by call, with regular flushes? While this use case may seem unusual, I think it could enable highly efficient interactive transactions without additional overhead. Currently, I have to implement a complex WASM VM rescheduling process to achieve similar behavior from an external thread. At what exact point does the behavior become undefined? Is it safe to use it within a box_on_shutdown handler? In the discussion about whether threads should be controlled by Tarantool or the user, I agree with @Gerold103. Tarantool’s fiber model has significant limitations when compared to what Rust or Golang can offer. Having your own thread with a Golang or Tokio event loop is a lifesaver. Some runtimes, like Tokio, allow you to choose whether to manage threads yourself or let the runtime handle them, but not all runtimes offer this flexibility. For example, WASI runtimes include a threading proposal that allows user applications to create threads independently. My WASM module relies on that, and as far as I know, there’s no elegant way to override threading handles in WASI while maintaining the same safety guarantees. That said, as you mentioned, Tarantool’s threads have certain non-ev-related setups, like signal masks, which are essential for applications. In my opinion, a good middle ground would be to offer handles like:
These would handle the non-ev setup. This approach is similar to what’s done in the wasm-micro-runtime, where, if you choose not to let your application manage threads, you can manage them manually. @locker mentioned that our Rust team is fine with no signal control and stuff, but the only reason they’re okay with it is because they don’t handle signal masks or implement any form of graceful shutdown, so the point is not really valid. |
Beta Was this translation helpful? Give feedback.
-
Sounds good to me. I'd prefer this variant to splitting the struct into
TBH I don't see the point in keeping a builtin pipe-to-tx in each cord. How would this help us? In a user-created thread the pipe would be stored in thread local storage, no? If yes, then what's the difference where to store our system pipes?
|
Beta Was this translation helpful? Give feedback.
-
@mkokryashkin and @sergos - please, have another look. The Open Question was resolved. |
Beta Was this translation helpful? Give feedback.
-
Reviewers
Changelog
v3:
cqueue
. Nowcpipe
can work both with and without libev.v2:
Summary
There is a demand for being able to send messages to the TX thread from other threads. The existing https://github.com/tarantool/xtm is not good enough. Its queue is limited and it won't work as well on Mac, which apparently is important at least for developers, even though it won't run in prod. Also it is an external tool and isn't very "native" to existing Tarantool event loop mechanisms.
It was suggested by Picodata folks that we export our
cbus
endpoints and a subset ofcpipe
. The idea was that the users via some sort of public API would be able to create their owncbus_endpoint
in TX thread, runcbus_loop()
on it in a user-fiber, create a customcpipe
in any other threads, connect them to this new endpoint, and send stuff there.Besides just suggesting it, the Picodata guys did us a big favor and brought a PR #9129 + took it on board in their fork, started using it, and now some of our projects started using this patch for PoCs as well. Clearly, the idea works well, but needs a bit of polishing before we merge it into vanilla Tarantool.
This document suggests a clear and extendible public API for doing that. However it goes a simpler route. Instead of exposing new public types like pipes and endpoints the proposal is to only expose a few functions which being called by any thread would send callbacks to the TX thread.
What we already have
cbus
is the "middleware" for exchanging messages between threads. It allows creation of endpoints which can receive messages.cbus_endpoint
is the endpoint. Each endpoint's name is globally unique in the whole process. Each endpoint's messages can be processed separately and independently from the other endpoints. For example, 2 fibers can be serving 2 different endpoints.cpipe
is a unidirectional message queue. The pipe can be created in any thread, connected to the given endpoint by name, and send messages to there.cbus_endpoint
is tightly integrated with libev. Specifically,cbus_endpoint
gets connected to the current thread's event-loop and its events ("new messages received") get delivered in the form of libev callbacks.cpipe
is actually not so much integrated with libev. Most of cpipe is just a size-limited forward-list ofcmsg
objects. Until the size is reached, the elements are pushed into the queue without any locks. When the size is reached, the queue is flushed into the destination endpoint under a mutex lock. The only integration with libev is thatcpipe
doesn't require an explicit flush of the queue. Instead, when a first item is pushed,cpipe
schedules its own flush in the end of the current event loop iteration.The original idea
Given that the needed usage is
cbus_endpoint
in TX thread andcpipe
in external threads, it looks like the current APIs are almost suitable as is. Picodata guys saw it too, and did the following:cbus_endpoint
into the public C API as is.cpipe
into a new simpler object calledlcpipe
(from "light"cpipe
).This
lcpipe
has all the libev stuff stripped. It is a simple queue which can be flushed into its destination endpoint (manually or when reaches the max size). The flush is still the mutex-locked move of the items +ev_async_send()
into the endpoint's file descriptor (hence waking it up in the target thread).The
lcpipe
andcbus_endpoint
APIs were made public as is.The suggested idea
This document revisits the original goal - being able to send callbacks to TX thread from other threads. For that the users really don't need any explicit public access to cbus endpoints or pipes. All what they really need is a function which sends a callback + its argument to TX thread.
The proposal is to do just that.
thread_local
data. Similar tocord_on_demand()
.box.cfg
.Public C API for external threads
Public API for TX thread
TX thread gets a new endpoint
"tx_user"
. It works similar to the"tx"
endpoint, which handles IProto requests. This endpoint has a fiber pool, whose size is configurable withbox.cfg.tx_user_pool_size
(default same asnet_msg_max
). The worker fibers inherit the pool's name.The pool is needed because the user callbacks are very likely to be doing fiber yields, which can be especially long if caused by WAL writes. A fiber pool would allow to execute new callbacks while the already running ones are waiting for something.
The pool size must be configurable, because unless the default is infinite, some users would always want to be able to run more callbacks in parallel.
Example of usage
TX thread
Any other non-TX thread (in C, but doesn't have to be)
Internal C API
Instead of creating a new type of pipe the decision is to make
cpipe
be able to work both with and without libev. It isn't so hard sincecpipe
doesn't really use libev much. It is only used for auto-flush, which can simply be disabled when a libev loop is not provied.The public functions
tnt_tx_push()
andtnt_tx_flush()
on first invocation in each thread are going to create a thread-localcpipe
without libev, connected to the"tx_user"
endpoint in TX thread.Alternatives
Expose
cbus_endpoint
and a light-cpipe
into the public API.It would work, but the API appeared to be requiring too many actions, like explicit creation of the pipe and the endpoint. It would give more freedom, but was discarded, because apparently isn't needed for the immediate task.
Extract a part of
cpipe
into a new structlcpipe
like in the original PR.That is too much code movements and renames and isn't really needed.
cpipe
is almost able to work without libev anyway.Beta Was this translation helpful? Give feedback.
All reactions