add tokio console support to repl tool and create a readme for the repl tool

This commit is contained in:
Simon Laux
2024-10-03 08:19:53 +02:00
parent aa3ef5011b
commit 2cd4af576a
4 changed files with 205 additions and 8 deletions

View File

@@ -7,6 +7,7 @@ repository = "https://github.com/deltachat/deltachat-core-rust"
[dependencies]
anyhow = { workspace = true }
console-subscriber = "0.4.0"
deltachat = { workspace = true, features = ["internals"]}
dirs = "5"
log = { workspace = true }
@@ -15,6 +16,7 @@ qr2term = "0.3.3"
rusqlite = { workspace = true }
rustyline = "14"
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
tracing = "0.1.40"
tracing-subscriber = { workspace = true, features = ["env-filter"] }
[features]

60
deltachat-repl/README.md Normal file
View File

@@ -0,0 +1,60 @@
# Delta Chat REPL
This is a simple [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) frontend build on top of delta chat core.
It's purpose is to help with quick testing during development, it is not meant for end users.
Dependencies:
- if you want to use `getqr` you need `qrencode` (macOS: `brew install qrencode`)
## Usage
```
cargo run <path to deltachat db>
```
Type in `help` to learn about what comands are available.
## Usage with `tokio-console`
Tokio is the async runtime that delta chat core uses.
Core uses tokio tasks, which is something similar to a thread.
`tokio-console` is like a task manager for these tokio-tasks.
Examples of tasks:
- The event loop in the repl tool which processes events received from core
- The repl loop itself which waits for and executes user commands
- The imap task that manages imap connection in core
```
RUSTFLAGS="--cfg tokio_unstable" cargo run <path to deltachat db>
```
Then in a new console window start [`tokio-console`](https://github.com/tokio-rs/console).
You can install it via `cargo install tokio-console`.
### Quick Example
An example session in the repl tool.
```
RUSTFLAGS="--cfg tokio_unstable" cargo run test-db/db
setqr dcaccount:https://nine.testrun.org/new
configure
connect
listchats
getqr
```
If it crashes you can just start it again and use the openpgp4fpr url instead of scanning the code from the terminal.
Or install `qrencode` to fix the crash and run `getqr` again.
Use the qrcode/openpgp4fpr link to setup the contact on deltachat.
Then write a message to that new contact, after that we can accept the chat in the repl tool and send a reply:
```
listchats
accept 12
chat 12
send hi!
chat
```

View File

@@ -30,7 +30,7 @@ use rustyline::{
};
use tokio::fs;
use tokio::runtime::Handle;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, Layer};
mod cmdline;
use self::cmdline::*;
@@ -317,7 +317,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
.await?;
let events = context.get_event_emitter();
tokio::task::spawn(async move {
spawn_named_task!("repl:receive_event", async move {
while let Some(event) = events.recv().await {
receive_event(event.typ);
}
@@ -333,7 +333,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
let mut selected_chat = ChatId::default();
let ctx = context.clone();
let input_loop = tokio::task::spawn_blocking(move || {
let input_loop = spawn_named_blocking_task!("repl:input_loop", move || {
let h = DcHelper {
completer: FilenameCompleter::new(),
highlighter: MatchingBracketHighlighter::new(),
@@ -481,11 +481,22 @@ async fn handle_cmd(
#[tokio::main]
async fn main() -> Result<(), Error> {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::from_default_env().add_directive("deltachat_repl=info".parse()?),
)
.init();
tracing::subscriber::set_global_default({
let subscribers = tracing_subscriber::Registry::default().with(
tracing_subscriber::fmt::layer().with_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("deltachat_repl=info".parse()?),
),
);
#[cfg(tokio_unstable)]
{
subscribers.with(console_subscriber::spawn())
}
#[cfg(not(tokio_unstable))]
{
subscribers
}
})?;
let args = std::env::args().collect();
start(args).await?;

View File

@@ -708,6 +708,130 @@ pub(crate) fn inc_and_check<T: PrimInt + AddAssign + std::fmt::Debug>(
Ok(())
}
/// Spawns a named asynchronous task if the `tokio_unstable` feature is enabled.
///
/// Spawns a new asynchronous task, returning a [tokio::task::JoinHandle] for it.
/// The provided future will start running in the background immediately when spawn is called, even if you don't await the returned JoinHandle.
/// See [tokio::task::spawn].
///
/// If the rustflag `tokio_unstable` is active, the task will be given the specified `name`
/// for easier identification in monitoring tools (like tokio-console).
/// If `tokio_unstable` is not set, the task is spawned normally without a name.
///
/// # Parameters
///
/// - `name`: The name of the task (used only if `tokio_unstable` is enabled).
/// - `future`: The future to be executed, which must implement `Future`, be `Send`, and `'static`.
///
/// # Returns
///
/// A [tokio::task::JoinHandle] that can be awaited to retrieve the output of the future.
///
/// # Panics
///
/// Panics if the task fails to spawn when `tokio_unstable` is enabled.
///
/// # Example
///
/// ```
/// use tokio::task;
///
/// let handle = spawn_named_task!("my_task", async {
/// // Your async code here
/// });
///
/// let result = handle.await.unwrap();
/// ```
#[macro_export]
macro_rules! spawn_named_task {
($name:expr, $future:expr) => {{
#[inline(always)]
pub fn __spawn_named_task<Fut>(
name: &str,
future: Fut,
) -> ::tokio::task::JoinHandle<Fut::Output>
where
Fut: ::std::future::Future + Send + 'static,
Fut::Output: Send + 'static,
{
#[cfg(tokio_unstable)]
{
::tokio::task::Builder::new()
.name(name)
.spawn(future)
.expect("Failed to spawn task")
}
#[cfg(not(tokio_unstable))]
{
::tokio::task::spawn(future)
}
}
__spawn_named_task($name, $future)
}};
}
/// Spawns a named blocking task if the `tokio_unstable` feature is enabled.
///
/// Spawns a new blocking task, returning a [tokio::task::JoinHandle] for it.
/// The provided future will start running in the background immediately when spawn is called, even if you don't await the returned JoinHandle.
/// See [tokio::task::spawn_blocking].
///
/// If the rustflag `tokio_unstable` is active, the task will be given the specified `name`
/// for easier identification in monitoring tools (like tokio-console).
/// If `tokio_unstable` is not set, the task is spawned normally without a name.
///
/// # Parameters
///
/// - `name`: The name of the task (used only if `tokio_unstable` is enabled).
/// - `future`: The future to be executed, which must implement `Future`, be `Send`, and `'static`.
///
/// # Returns
///
/// A [tokio::task::JoinHandle] that can be awaited to retrieve the output of the future.
///
/// # Panics
///
/// Panics if the task fails to spawn when `tokio_unstable` is enabled.
///
/// # Example
///
/// ```
/// use tokio::task;
///
/// let handle = spawn_named_blocking_task!("my_task", async {
/// // Your async code here
/// });
///
/// let result = handle.await.unwrap();
/// ```
#[macro_export]
macro_rules! spawn_named_blocking_task {
($name:expr, $future:expr) => {{
#[inline(always)]
pub fn __spawn_named_blocking_task<Fut, ReturnType>(
name: &str,
future: Fut,
) -> ::tokio::task::JoinHandle<ReturnType>
where
Fut: FnOnce() -> ReturnType + Send + 'static,
ReturnType: Send + 'static,
{
#[cfg(tokio_unstable)]
{
::tokio::task::Builder::new()
.name(name)
.spawn_blocking(future)
.expect("Failed to spawn task")
}
#[cfg(not(tokio_unstable))]
{
::tokio::task::spawn_blocking(future)
}
}
__spawn_named_blocking_task($name, $future)
}};
}
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]