schmoozer

Check-in Differences
Login

Check-in Differences

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From schmoozer-0.1.2 To schmoozer-0.2.0

2024-10-03
00:30
Change trait methods return types. Add ConnResult and RunResult. check-in: 2924879fbf user: jan tags: trunk
2024-09-16
05:25
Style fixups. check-in: de2e43c6de user: jan tags: schmoozer-0.2.0, trunk
05:24
Exclude backon.toml from packaging. check-in: 42c4c94c32 user: jan tags: trunk
2024-03-28
17:38
Fix typo. check-in: cdd924e4cd user: jan tags: trunk
2024-03-01
00:20
Enable feature labels in docs. check-in: b6c565a755 user: jan tags: schmoozer-0.1.2, trunk
00:11
docsrs label for tcpconn. check-in: 6ba6d163f7 user: jan tags: trunk

Changes to Cargo.toml.

1
2
3
4
5


6
7
8
9
10
11
12
13

14

15
16




17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34






[package]
name = "schmoozer"
version = "0.1.2"
edition = "2021"
license = "0BSD"


keywords = [ "connector", "network", "tokio" ]
repository = "https://repos.qrnch.tech/pub/schmoozer"
description = "A simple abstraction over a retryable async operation"
rust-version = "1.74"
exclude = [
  ".fossil-settings",
  ".efiles",
  ".fslckout",

  "www",

  "rustfmt.toml"
]





[features]
tcpconn = ["dep:killswitch", "tokio/macros", "tokio/net", "tokio/time"]

[dependencies]
async-trait = { version = "0.1.77" }
killswitch = { version = "0.4.2", optional = true }
tokio = { version = "1.36.0", optional = true }

[dev-dependencies]
tokio = { version = "1.36.0", features = [
  "macros", "net", "rt-multi-thread", "time"
] }

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]









|


>
>


|





>

>


>
>
>
>





|

|


|







>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[package]
name = "schmoozer"
version = "0.2.0"
edition = "2021"
license = "0BSD"
# https://crates.io/category_slugs
categories = [ "network-programming" ]
keywords = [ "connector", "network", "tokio" ]
repository = "https://repos.qrnch.tech/pub/schmoozer"
description = "A simple abstraction over a retryable async operation, such as establishing a connection"
rust-version = "1.74"
exclude = [
  ".fossil-settings",
  ".efiles",
  ".fslckout",
  "examples",
  "www",
  "bacon.toml",
  "rustfmt.toml"
]

# https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section
[badges]
maintenance = { status = "actively-developed" }

[features]
tcpconn = ["dep:killswitch", "tokio/macros", "tokio/net", "tokio/time"]

[dependencies]
async-trait = { version = "0.1.82" }
killswitch = { version = "0.4.2", optional = true }
tokio = { version = "1.40.0", optional = true }

[dev-dependencies]
tokio = { version = "1.40.0", features = [
  "macros", "net", "rt-multi-thread", "time"
] }

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]

[lints.clippy]
all = { level = "deny", priority = -1 }
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }

Added bacon.toml.















































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# This is a configuration file for the bacon tool
#
# Bacon repository: https://github.com/Canop/bacon
# Complete help on configuration: https://dystroy.org/bacon/config/
# You can also check bacon's own bacon.toml file
#  as an example: https://github.com/Canop/bacon/blob/main/bacon.toml

default_job = "clippy-all"

[jobs.check]
command = ["cargo", "check", "--color", "always"]
need_stdout = false

[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--color", "always"]
need_stdout = false

# Run clippy on the default target
[jobs.clippy]
command = [
    "cargo", "clippy",
    "--all-features",
    "--color", "always",
]
need_stdout = false

# Run clippy on all targets
# To disable some lints, you may change the job this way:
#    [jobs.clippy-all]
#    command = [
#        "cargo", "clippy",
#        "--all-targets",
#        "--color", "always",
#    	 "--",
#    	 "-A", "clippy::bool_to_int_with_if",
#    	 "-A", "clippy::collapsible_if",
#    	 "-A", "clippy::derive_partial_eq_without_eq",
#    ]
# need_stdout = false
[jobs.clippy-all]
command = [
    "cargo", "clippy",
    "--all-features",
    "--all-targets",
    "--color", "always",
]
need_stdout = false

# This job lets you run
# - all tests: bacon test
# - a specific test: bacon test -- config::test_default_files
# - the tests of a package: bacon test -- -- -p config
[jobs.test]
command = [
    "cargo", "test", "--color", "always",
    "--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true

[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false

# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change

# You can run your application and have the result displayed in bacon,
# *if* it makes sense for this crate.
# Don't forget the `--color always` part or the errors won't be
# properly parsed.
# If your program never stops (eg a server), you may set `background`
# to false to have the cargo run output immediately displayed instead
# of waiting for program's end.
[jobs.run]
command = [
    "cargo", "run",
    "--color", "always",
    # put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = true

# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
#    bacon ex -- my-example
[jobs.ex]
command = ["cargo", "run", "--color", "always", "--example"]
need_stdout = true
allow_warnings = true

# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target

Changes to examples/net.rs.

1
2
3
4
5
6
7
8
9
10
11
12

13
14
15
16
17
18
19
use std::{env, io::ErrorKind, ops::ControlFlow, time::Duration};

pub use tokio::net::TcpStream;

use schmoozer::{async_trait, Connector};

pub struct TcpConnector {
  addr: String,
  delay: usize
}

impl TcpConnector {

  pub fn new(addr: impl ToString) -> Self {
    Self {
      addr: addr.to_string(),
      delay: 1
    }
  }
}












>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use std::{env, io::ErrorKind, ops::ControlFlow, time::Duration};

pub use tokio::net::TcpStream;

use schmoozer::{async_trait, Connector};

pub struct TcpConnector {
  addr: String,
  delay: usize
}

impl TcpConnector {
  #[allow(clippy::needless_pass_by_value)]
  pub fn new(addr: impl ToString) -> Self {
    Self {
      addr: addr.to_string(),
      delay: 1
    }
  }
}
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    match res {
      Ok(conn) => Ok(conn),
      Err(e) => match e.kind() {
        ErrorKind::ConnectionRefused
        | ErrorKind::ConnectionAborted
        | ErrorKind::NotConnected
        | ErrorKind::TimedOut => {
          println!("Retryable error: {}", e);
          Err(ControlFlow::Continue(e))
        }
        _ => {
          println!("Fatal error: {}", e);
          Err(ControlFlow::Break(e))
        }
      }
    }
  }

  async fn retry_delay(&mut self) -> ControlFlow<Self::Error> {
    let dur = Duration::from_secs(self.delay.try_into().unwrap());
    println!("Retrying in {:?} ..", dur);

    tokio::time::sleep(dur).await;

    // for next iteration double sleep duration for each iteration, but cap at
    // 60 seconds
    self.delay = std::cmp::min(self.delay * 2, 60);
    ControlFlow::Continue(())







|



|








|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    match res {
      Ok(conn) => Ok(conn),
      Err(e) => match e.kind() {
        ErrorKind::ConnectionRefused
        | ErrorKind::ConnectionAborted
        | ErrorKind::NotConnected
        | ErrorKind::TimedOut => {
          println!("Retryable error: {e}");
          Err(ControlFlow::Continue(e))
        }
        _ => {
          println!("Fatal error: {e}");
          Err(ControlFlow::Break(e))
        }
      }
    }
  }

  async fn retry_delay(&mut self) -> ControlFlow<Self::Error> {
    let dur = Duration::from_secs(self.delay.try_into().unwrap());
    println!("Retrying in {dur:?} ..");

    tokio::time::sleep(dur).await;

    // for next iteration double sleep duration for each iteration, but cap at
    // 60 seconds
    self.delay = std::cmp::min(self.delay * 2, 60);
    ControlFlow::Continue(())

Changes to examples/tcpconn.rs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[cfg(feature = "tcpconn")]
mod inner {
  use std::{env, ops::ControlFlow};

  use schmoozer::tcpconn::{KillSwitch, SimpleTcpConnector, TcpStream};

  pub(super) async fn main() {
    let args: Vec<String> = env::args().skip(1).collect();

    let ks = KillSwitch::new();
    let connector =
      SimpleTcpConnector::new(&args[0], ks, Box::new(proc_connection));

    schmoozer::run(connector).await.unwrap();






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[cfg(feature = "tcpconn")]
mod inner {
  use std::{env, ops::ControlFlow};

  use schmoozer::tcpconn::{KillSwitch, SimpleTcpConnector, TcpStream};

  pub async fn main() {
    let args: Vec<String> = env::args().skip(1).collect();

    let ks = KillSwitch::new();
    let connector =
      SimpleTcpConnector::new(&args[0], ks, Box::new(proc_connection));

    schmoozer::run(connector).await.unwrap();

Changes to src/lib.rs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//! _schmoozer_ is intended to be used as an `async` (re)connector.  It
//! consists of two parts:
//! - The [`Connector`] trait is implemented by applications/libraries that
//!   need to run retryable connection loops.
//! - [`run()`] is a function that takes in a `Connector` implementation, and
//!   attempts to establish a connection, delaying and retrying on failures
//!   that the callback reports as retriable, and calls the
//!   [`Connector::run()`] trait method once a connection has been successfully
//!   been established.
//!
//! Perhaps paradoxically the [`run()`] function does not itself actually
//! attemtp to establish any connections -- it relies on the `Connector` trait
//! to implement the means to establish connections.
//!
//! The "good path" overall flow of the connector loop is to call the
//! `connect()` method. If it is successful, call the `run()` method, passing
//! along the newly allocated connection. The main application logic relating
//! to the connection should called from this method.
//!











|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//! _schmoozer_ is intended to be used as an `async` (re)connector.  It
//! consists of two parts:
//! - The [`Connector`] trait is implemented by applications/libraries that
//!   need to run retryable connection loops.
//! - [`run()`] is a function that takes in a `Connector` implementation, and
//!   attempts to establish a connection, delaying and retrying on failures
//!   that the callback reports as retriable, and calls the
//!   [`Connector::run()`] trait method once a connection has been successfully
//!   been established.
//!
//! Perhaps paradoxically the [`run()`] function does not itself actually
//! attempt to establish any connections -- it relies on the `Connector` trait
//! to implement the means to establish connections.
//!
//! The "good path" overall flow of the connector loop is to call the
//! `connect()` method. If it is successful, call the `run()` method, passing
//! along the newly allocated connection. The main application logic relating
//! to the connection should called from this method.
//!
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#[cfg(feature = "tcpconn")]
pub use tcpconn::SimpleTcpConnector;

/// Application callbacks for the [`run()`] function (or equivalent).
#[async_trait]
pub trait Connector {
  /// The connection type.
  type ConnType;

  /// The application error return type.
  type Error;

  /// Establish a connection.
  ///
  /// If a connection was successfully established the implementation should
  /// return `Ok(Self::ConnType)`.
  ///
  /// If an error is returned as `Err(ControlFlow::Continue(Self::Error))` it







|


|







41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#[cfg(feature = "tcpconn")]
pub use tcpconn::SimpleTcpConnector;

/// Application callbacks for the [`run()`] function (or equivalent).
#[async_trait]
pub trait Connector {
  /// The connection type.
  type ConnType: Send;

  /// The application error return type.
  type Error: Send;

  /// Establish a connection.
  ///
  /// If a connection was successfully established the implementation should
  /// return `Ok(Self::ConnType)`.
  ///
  /// If an error is returned as `Err(ControlFlow::Continue(Self::Error))` it
96
97
98
99
100
101
102

103
104
105



106
107
108
109
110
111
112
///
/// # Exit conditions
/// The (re)connection loop will keep running until an exit condition has been
/// triggered:
/// - [`Connector::connect()`] returns `Err(ControlFlow::Break(Self::Error))`
/// - [`Connector::retry_delay()`] returns `ControlFlow::Break(Self::Error)`
/// - [`Connector::run()`] returns `ControlFlow::Break(_)`

pub async fn run<E>(
  mut connector: impl Connector<Error = E>
) -> Result<(), E> {



  loop {
    // Call the application's connect callback to attempt to establish
    // connection.
    match connector.connect().await {
      Ok(conn) => {
        // A connection was successfully established -- call the run()
        // implementation.







>

|
|
>
>
>







96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
///
/// # Exit conditions
/// The (re)connection loop will keep running until an exit condition has been
/// triggered:
/// - [`Connector::connect()`] returns `Err(ControlFlow::Break(Self::Error))`
/// - [`Connector::retry_delay()`] returns `ControlFlow::Break(Self::Error)`
/// - [`Connector::run()`] returns `ControlFlow::Break(_)`
#[allow(clippy::missing_errors_doc)]
pub async fn run<E>(
  mut connector: impl Connector<Error = E> + Send
) -> Result<(), E>
where
  E: Send
{
  loop {
    // Call the application's connect callback to attempt to establish
    // connection.
    match connector.connect().await {
      Ok(conn) => {
        // A connection was successfully established -- call the run()
        // implementation.
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
    }

    // If this point is reached the application has requested a reconnection.
    // Call `retry_delay()` to allow the application to determine whether to
    // retry or not.

    match connector.retry_delay().await {
      ControlFlow::Continue(_) => {
        // Application wants to reconnect.
        continue;
      }
      ControlFlow::Break(err) => {
        // Application does not want to reconnect
        break Err(err);
      }
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







|












137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
    }

    // If this point is reached the application has requested a reconnection.
    // Call `retry_delay()` to allow the application to determine whether to
    // retry or not.

    match connector.retry_delay().await {
      ControlFlow::Continue(()) => {
        // Application wants to reconnect.
        continue;
      }
      ControlFlow::Break(err) => {
        // Application does not want to reconnect
        break Err(err);
      }
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/tcpconn.rs.

29
30
31
32
33
34
35

36
37
38
39
40
41
42
  F: Future<
    Output = ControlFlow<
      Result<(), std::io::Error>,
      Result<(), std::io::Error>
    >
  >
{

  pub fn new(
    addr: impl ToString,
    ks: KillSwitch,
    cb: Box<dyn Fn(TcpStream, KillSwitch) -> F + Send>
  ) -> Self {
    Self {
      addr: addr.to_string(),







>







29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  F: Future<
    Output = ControlFlow<
      Result<(), std::io::Error>,
      Result<(), std::io::Error>
    >
  >
{
  #[allow(clippy::needless_pass_by_value)]
  pub fn new(
    addr: impl ToString,
    ks: KillSwitch,
    cb: Box<dyn Fn(TcpStream, KillSwitch) -> F + Send>
  ) -> Self {
    Self {
      addr: addr.to_string(),
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
                ErrorKind::NotConnected | ErrorKind::TimedOut => {
              Err(ControlFlow::Continue(e))
            }
            _ => Err(ControlFlow::Break(e))
          }
        }
      }
      _ = self.ks.wait() => {
        // Aborted -- use ErrorKind::Other to signal abortion
        let err = std::io::Error::other(String::from("aborted"));
        Err(ControlFlow::Break(err))
      }
    }
  }

  async fn retry_delay(&mut self) -> ControlFlow<Self::Error> {
    let dur = Duration::from_secs(self.delay.try_into().unwrap());
    tokio::select! {
      _ = self.ks.wait() => {
        let err = std::io::Error::other(String::from("aborted"));
        ControlFlow::Break(err)
      }
      _ = tokio::time::sleep(dur) => {
        // double sleep duration for each iteration, but cap at 60 seconds
        self.delay = std::cmp::min(self.delay * 2, 60);
        ControlFlow::Continue(())
      }
    }
  }








|










|



|







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
                ErrorKind::NotConnected | ErrorKind::TimedOut => {
              Err(ControlFlow::Continue(e))
            }
            _ => Err(ControlFlow::Break(e))
          }
        }
      }
      () = self.ks.wait() => {
        // Aborted -- use ErrorKind::Other to signal abortion
        let err = std::io::Error::other(String::from("aborted"));
        Err(ControlFlow::Break(err))
      }
    }
  }

  async fn retry_delay(&mut self) -> ControlFlow<Self::Error> {
    let dur = Duration::from_secs(self.delay.try_into().unwrap());
    tokio::select! {
      () = self.ks.wait() => {
        let err = std::io::Error::other(String::from("aborted"));
        ControlFlow::Break(err)
      }
      () = tokio::time::sleep(dur) => {
        // double sleep duration for each iteration, but cap at 60 seconds
        self.delay = std::cmp::min(self.delay * 2, 60);
        ControlFlow::Continue(())
      }
    }
  }

Changes to www/changelog.md.

1
2
3
4
5
6
7
8
9
10
11
12










13
14
15
16
17
18
19
# Change Log

## [Unreleased]

[Details](/vdiff?from=schmoozer-0.1.2&to=trunk)

### Added

### Changed

### Removed











---

## [0.1.2] - 2024-03-02

[Details](/vdiff?from=schmoozer-0.1.1&to=schmoozer-0.1.2)

### Changed




|







>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Change Log

## [Unreleased]

[Details](/vdiff?from=schmoozer-0.2.0&to=trunk)

### Added

### Changed

### Removed

---

## [0.2.0] - 2024-09-16

[Details](/vdiff?from=schmoozer-0.1.2&to=schmoozer-0.2.0)

### Changed

- Add `Send` bounds to ensure that `Future`s are `Send`.

---

## [0.1.2] - 2024-03-02

[Details](/vdiff?from=schmoozer-0.1.1&to=schmoozer-0.1.2)

### Changed

Changes to www/index.md.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# schmoozer

A simple abstraction over a retryable async operation, such as establishing a
TCP connection.


## Feature labels in documentation

The crate's documentation uses automatically generated feature labels, which
currently requires nightly featuers.  To build the documentation locally use:

```
$ RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" \
cargo +nightly doc --all-features
```


## Change log

The details of changes can always be found in the timeline, but for a
high-level view of changes between released versions there's a manually












|
<







1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20
# schmoozer

A simple abstraction over a retryable async operation, such as establishing a
TCP connection.


## Feature labels in documentation

The crate's documentation uses automatically generated feature labels, which
currently requires nightly featuers.  To build the documentation locally use:

```
$ RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features

```


## Change log

The details of changes can always be found in the timeline, but for a
high-level view of changes between released versions there's a manually