Skip to content

Non-spawning API for master and outstation creation #414

@jadamcrain

Description

@jadamcrain

Problem

The DNP3 library calls tokio::spawn internally in all spawn_* creation functions. This prevents callers from controlling which Tokio runtime or task set the futures are placed on.

Proposed Changes

Scope: TCP and serial transports. Rust API only — no FFI changes.

1. Master: builder + single task type

Add a MasterTask builder that separates configuration from execution:

let task = MasterTask::builder(config)
    .enable()
    .add_association(address, assoc_config, read_handler, assoc_handler, assoc_info)
    .tcp(endpoints, connect_strategy, listener)
    // or .serial(path, settings, retry_delay, listener)

MasterTask is a single concrete type regardless of transport. It exposes:

  • channel(&self) -> &MasterChannel — get the handle for post-spawn use
  • enable(), add_association() — synchronous, direct state mutation (no channel messages)
  • async fn run(self) — consumes the task and runs the event loop

The existing spawn_* functions are refactored to delegate to this, so no behavior changes for current users.

2. Outstation: same pattern

Same approach for outstation TCP client and serial — a single task type with pre-spawn configuration and async fn run(self).

3. Outstation TCP server: accept pre-bound TcpListener

Add a bind_with_listener(self, TcpListener) method that takes an already-bound listener synchronously. Refactor bind_no_spawn() to delegate to it.

Non-goals

  • FFI / language binding changes
  • TLS transport
  • Changes to existing public config types (semver)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions