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






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.1.2"
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"
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.77" }
async-trait = { version = "0.1.82" }
killswitch = { version = "0.4.2", optional = true }
tokio = { version = "1.36.0", optional = true }
tokio = { version = "1.40.0", optional = true }

[dev-dependencies]
tokio = { version = "1.36.0", features = [
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
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
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);
          println!("Retryable error: {e}");
          Err(ControlFlow::Continue(e))
        }
        _ => {
          println!("Fatal error: {}", 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);
    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
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() {
  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
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
//! 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
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;
  type ConnType: Send;

  /// The application error return type.
  type Error;
  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
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>
) -> Result<(), 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
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(_) => {
      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
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
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() => {
      () = 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() => {
      () = self.ks.wait() => {
        let err = std::io::Error::other(String::from("aborted"));
        ControlFlow::Break(err)
      }
      _ = tokio::time::sleep(dur) => {
      () = 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
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.1.2&to=trunk)
[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
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" \
$ RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
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