Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 2 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ serde = { version = "1", features = ["derive"] }
bincode = "2"
tokio = { version = "1", default-features = false, features = ["net", "signal", "rt-multi-thread", "macros", "sync", "time"] }
futures = "0.3"
num_cpus = "^1"
async-trait = "0.1"
bytes = "1"

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ WireframeServer::new(|| {
.await
```

By default, the number of worker tasks equals the number of CPU cores. If the
CPU count cannot be determined, the server falls back to a single worker.

The builder supports methods like `frame_processor`, `route`, `app_data`, and
`wrap` for middleware configuration【F:docs/rust-binary-router-library-design.md†L616-L704】.

Expand Down
4 changes: 3 additions & 1 deletion docs/rust-binary-router-library-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,9 @@ similar to Actix Web's web::Data.21
`HttpServer`) would take the configured `WireframeApp` factory (a closure that
creates an `App` instance per worker thread), bind to a network address, and
manage incoming connections, task spawning for each connection, and the
overall server lifecycle. This would likely be built on Tokio's networking and
overall server lifecycle. The default number of worker tasks matches the
available CPU cores, falling back to a single worker if the count cannot be
determined. This would likely be built on Tokio's networking and
runtime primitives.18

This structural similarity to Actix Web is intentional. Developers familiar with
Expand Down
46 changes: 46 additions & 0 deletions src/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ impl<T: Send + Sync> SharedState<T> {
/// assert_eq!(*state, 5);
/// ```
#[must_use]
/// Creates a new `SharedState` instance wrapping the provided `Arc<T>`.
///
/// # Examples
///
/// ```
/// use std::sync::Arc;
/// use wireframe::extractor::SharedState;
/// let state = Arc::new(42);
/// let shared = SharedState::new(state.clone());
/// assert_eq!(*shared, 42);
/// ```
pub fn new(inner: Arc<T>) -> Self {
Self(inner)
}
Expand Down Expand Up @@ -123,8 +134,43 @@ impl<T: Send + Sync> std::ops::Deref for SharedState<T> {
/// let state = Arc::new(42);
/// let shared = SharedState::new(state.clone());
/// assert_eq!(*shared, 42);
/// Returns a reference to the inner shared state value.
///
/// Allows transparent access to the wrapped state as if it were a reference to the underlying type.
///
/// # Examples
///
/// ```
/// use std::sync::Arc;
/// use wireframe::extractor::SharedState;
///
/// let state = Arc::new(42);
/// let shared = SharedState::new(state);
/// assert_eq!(*shared, 42);
/// ```
fn deref(&self) -> &Self::Target {
&self.0
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn advance_consumes_bytes() {
let mut payload = Payload { data: b"hello" };
payload.advance(2);
assert_eq!(payload.data, b"llo");
payload.advance(10);
assert!(payload.data.is_empty());
}

#[test]
fn remaining_reports_length() {
let mut payload = Payload { data: b"abc" };
assert_eq!(payload.remaining(), 3);
payload.advance(1);
assert_eq!(payload.remaining(), 2);
}
}
20 changes: 19 additions & 1 deletion src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ where
/// Create a new [`Next`] wrapping the given service.
#[inline]
#[must_use]
/// Creates a new `Next` instance wrapping a reference to the given service.
///
/// # Examples
///
/// ```
/// # use your_crate::{Next, Service, ServiceRequest};
/// # struct MyService;
/// # impl Service for MyService {
/// # type Error = std::convert::Infallible;
/// # async fn call(&self, _req: ServiceRequest) -> Result<super::ServiceResponse, Self::Error> {
/// # Ok(super::ServiceResponse)
/// # }
/// # }
/// let service = MyService;
/// let next = Next::new(&service);
/// ```
pub fn new(service: &'a S) -> Self {
Self { service }
}
Expand All @@ -31,7 +47,9 @@ where
///
/// # Errors
Comment thread
leynos marked this conversation as resolved.
///
/// Propagates any error produced by the wrapped service.
/// Asynchronously invokes the wrapped service with the given request.
///
/// Returns a response produced by the service, or an error if the service fails to handle the request.
#[must_use = "await the returned future"]
pub async fn call(&self, req: ServiceRequest) -> Result<ServiceResponse, S::Error> {
self.service.call(req).await
Expand Down
68 changes: 65 additions & 3 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,85 @@ impl<F> WireframeServer<F>
where
F: Fn() -> WireframeApp + Send + Sync + Clone + 'static,
{
/// Construct a new server using the supplied application factory.
/// Constructs a new `WireframeServer` using the provided application factory
/// closure.
///
/// The default worker count equals the number of CPU cores.
///
/// If the CPU count cannot be determined, the server defaults to a single
/// worker.
///
/// ```no_run
/// use wireframe::{app::WireframeApp, server::WireframeServer};
///
/// let factory = || WireframeApp::new().unwrap();
/// let server = WireframeServer::new(factory);
/// Creates a new `WireframeServer` with the provided factory closure.
///
/// The server is initialised with a default worker count equal to the number of available CPU cores, or 1 if this cannot be determined. The TCP listener is unset and must be configured with `bind` before running the server.
///
/// # Panics
///
/// Panics if the number of available CPUs cannot be determined and the fallback to 1 fails.
Comment thread
leynos marked this conversation as resolved.
///
/// # Examples
///
/// ```
/// let server = WireframeServer::new(|| WireframeApp::default());
/// assert!(server.worker_count() >= 1);
/// ```
#[must_use]
pub fn new(factory: F) -> Self {
let workers = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
Self {
factory,
listener: None,
workers: num_cpus::get(),
workers,
}
}

/// Set the number of worker tasks to spawn.
/// Set the number of worker tasks to spawn for the server.
///
/// Ensures at least one worker is configured.
///
/// # Examples
///
/// ```ignore
/// let server = WireframeServer::new(factory).workers(4);
/// ```
#[must_use]
/// Sets the number of worker tasks to spawn, ensuring at least one worker is configured.
///
/// Returns a new `WireframeServer` instance with the updated worker count. If `count` is less than 1, it defaults to 1.
///
/// # Examples
///
/// ```
/// let server = WireframeServer::new(factory).workers(4);
/// assert_eq!(server.worker_count(), 4);
/// let server = server.workers(0);
/// assert_eq!(server.worker_count(), 1);
/// ```
pub fn workers(mut self, count: usize) -> Self {
self.workers = count.max(1);
self
}

/// Get the configured worker count.
#[inline]
#[must_use]
/// Returns the configured number of worker tasks for the server.
///
/// # Examples
///
/// ```
/// let server = WireframeServer::new(factory);
/// assert!(server.worker_count() >= 1);
/// ```
pub const fn worker_count(&self) -> usize {
self.workers
}

/// Bind the server to the given address and create a listener.
///
/// # Errors
Expand Down
22 changes: 22 additions & 0 deletions tests/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use wireframe::{app::WireframeApp, server::WireframeServer};

#[test]
fn default_worker_count_matches_cpu_count() {
let server = WireframeServer::new(|| WireframeApp::new().expect("WireframeApp::new failed"));
let expected = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
assert_eq!(server.worker_count(), expected);
}

#[test]
fn workers_method_enforces_minimum() {
let server =
WireframeServer::new(|| WireframeApp::new().expect("WireframeApp::new failed")).workers(0);
assert_eq!(server.worker_count(), 1);
}

#[test]
fn workers_accepts_large_values() {
let server = WireframeServer::new(|| WireframeApp::new().expect("WireframeApp::new failed"))
.workers(128);
assert_eq!(server.worker_count(), 128);
}
Loading