Skip to content

Commit

Permalink
*: Feature gate async runtime, allow opting between Tokio or smol (#27)
Browse files Browse the repository at this point in the history
For every OS each `IfWatcher` is under the `tokio` or `smol` module.
  • Loading branch information
jxs authored Nov 4, 2022
1 parent f265827 commit 31c4780
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:

- name: Build android
if: contains(matrix.platform.target, 'android')
run: cargo apk --target ${{ matrix.platform.target }} build --workspace --all-features
run: cargo apk build --target ${{ matrix.platform.target }} --all-features

- name: Rust tests
if: matrix.platform.cross == false
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.0.0]

### Changed
- Feature gate async runtime, allowing opting between Tokio or smol. For every OS each `IfWatcher` is
under the `tokio` or `smol` module. This makes it a breaking change as there
is no more a default implementation. See [PR 27](https://github.com/mxinden/if-watch/pull/27).

## [2.0.0]

### Changed
Expand Down
20 changes: 18 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
[package]
name = "if-watch"
version = "2.0.0"
version = "3.0.0"
authors = ["David Craven <[email protected]>", "Parity Technologies Limited <[email protected]>"]
edition = "2021"
keywords = ["asynchronous", "routing"]
license = "MIT OR Apache-2.0"
description = "crossplatform asynchronous network watcher"
repository = "https://github.com/mxinden/if-watch"

[lib]
crate-type = ["cdylib", "lib"]

[features]
tokio = ["dep:tokio", "rtnetlink/tokio_socket"]
smol = ["dep:smol", "rtnetlink/smol_socket"]

[dependencies]
fnv = "1.0.7"
futures = "0.3.19"
ipnet = "2.3.1"
log = "0.4.14"

[target.'cfg(target_os = "linux")'.dependencies]
rtnetlink = { version = "0.10.0", default-features = false, features = ["smol_socket"] }
rtnetlink = { version = "0.10.0", default-features = false }

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
core-foundation = "0.9.2"
if-addrs = "0.7.0"
system-configuration = "0.5.0"
tokio = { version = "1.21.2", features = ["rt"], optional = true }
smol = { version = "1.2.5", optional = true }

[target.'cfg(target_os = "windows")'.dependencies]
if-addrs = "0.7.0"
Expand All @@ -32,3 +41,10 @@ if-addrs = "0.7.0"

[dev-dependencies]
env_logger = "0.9.0"
smol = "1.2.5"
tokio = { version = "1.21.2", features = ["rt", "macros"] }

[[example]]
name = "if_watch"
required-features = ["smol"]

4 changes: 2 additions & 2 deletions examples/if_watch.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use futures::StreamExt;
use if_watch::IfWatcher;
use if_watch::smol::IfWatcher;

fn main() {
env_logger::init();
futures::executor::block_on(async {
smol::block_on(async {
let mut set = IfWatcher::new().unwrap();
loop {
let event = set.select_next_some().await;
Expand Down
37 changes: 36 additions & 1 deletion src/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
use core_foundation::string::CFString;
use fnv::FnvHashSet;
use futures::channel::mpsc;
use futures::stream::Stream;
use futures::stream::{FusedStream, Stream};
use if_addrs::IfAddr;
use std::collections::VecDeque;
use std::io::Result;
Expand All @@ -14,6 +14,26 @@ use system_configuration::dynamic_store::{
SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext,
};

#[cfg(feature = "tokio")]
pub mod tokio {
//! An interface watcher.
//! **On Apple Platforms there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

#[cfg(feature = "smol")]
pub mod smol {
//! An interface watcher.
//! **On Apple platforms there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

#[derive(Debug)]
pub struct IfWatcher {
addrs: FnvHashSet<IpNet>,
Expand Down Expand Up @@ -55,10 +75,12 @@ impl IfWatcher {
Ok(())
}

/// Iterate over current networks.
pub fn iter(&self) -> impl Iterator<Item = &IpNet> {
self.addrs.iter()
}

/// Poll for an address change event.
pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll<Result<IfEvent>> {
loop {
if let Some(event) = self.queue.pop_front() {
Expand All @@ -74,6 +96,19 @@ impl IfWatcher {
}
}

impl Stream for IfWatcher {
type Item = Result<IfEvent>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::into_inner(self).poll_if_event(cx).map(Some)
}
}

impl FusedStream for IfWatcher {
fn is_terminated(&self) -> bool {
false
}
}

fn ifaddr_to_ipnet(addr: IfAddr) -> IpNet {
match addr {
IfAddr::V4(ip) => {
Expand Down
40 changes: 37 additions & 3 deletions src/fallback.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
use crate::IfEvent;
use async_io::Timer;
use futures::stream::Stream;
use futures::stream::{FusedStream, Stream};
use if_addrs::IfAddr;
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use std::collections::{HashSet, VecDeque};
use std::future::Future;
use std::io::Result;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};

#[cfg(feature = "tokio")]
pub mod tokio {
//! An interface watcher.
//! **On this platform there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

#[cfg(feature = "smol")]
pub mod smol {
//! An interface watcher.
//! **On this platform there is no difference between `tokio` and `smol` features,**
//! **this was done to maintain the api compatible with other platforms**.

/// Watches for interface changes.
pub type IfWatcher = super::IfWatcher;
}

/// An address set/watcher
#[derive(Debug)]
pub struct IfWatcher {
Expand All @@ -19,7 +38,7 @@ pub struct IfWatcher {
}

impl IfWatcher {
/// Create a watcher
/// Create a watcher.
pub fn new() -> Result<Self> {
Ok(Self {
addrs: Default::default(),
Expand All @@ -45,10 +64,12 @@ impl IfWatcher {
Ok(())
}

/// Iterate over current networks.
pub fn iter(&self) -> impl Iterator<Item = &IpNet> {
self.addrs.iter()
}

/// Poll for an address change event.
pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll<Result<IfEvent>> {
loop {
if let Some(event) = self.queue.pop_front() {
Expand All @@ -64,6 +85,19 @@ impl IfWatcher {
}
}

impl Stream for IfWatcher {
type Item = Result<IfEvent>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::into_inner(self).poll_if_event(cx).map(Some)
}
}

impl FusedStream for IfWatcher {
fn is_terminated(&self) -> bool {
false
}
}

fn ifaddr_to_ipnet(addr: IfAddr) -> IpNet {
match addr {
IfAddr::V4(ip) => {
Expand Down
114 changes: 62 additions & 52 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@
#![deny(missing_docs)]
#![deny(warnings)]

use futures::stream::FusedStream;
use futures::Stream;
pub use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use std::io::Result;
use std::pin::Pin;
use std::task::{Context, Poll};

#[cfg(target_os = "macos")]
mod apple;
Expand All @@ -25,21 +20,47 @@ mod linux;
#[cfg(target_os = "windows")]
mod win;

#[cfg(target_os = "macos")]
use apple as platform_impl;
#[cfg(target_os = "ios")]
use apple as platform_impl;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(feature = "tokio")]
pub use apple::tokio;

#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(feature = "smol")]
pub use apple::smol;

#[cfg(feature = "smol")]
#[cfg(not(any(
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "windows",
)))]
use fallback as platform_impl;
#[cfg(target_os = "linux")]
use linux as platform_impl;
pub use fallback::smol;

#[cfg(feature = "tokio")]
#[cfg(not(any(
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "windows",
)))]
pub use fallback::tokio;

#[cfg(target_os = "windows")]
use win as platform_impl;
#[cfg(feature = "tokio")]
pub use win::tokio;

#[cfg(target_os = "windows")]
#[cfg(feature = "smol")]
pub use win::smol;

#[cfg(target_os = "linux")]
#[cfg(feature = "tokio")]
pub use linux::tokio;

#[cfg(target_os = "linux")]
#[cfg(feature = "smol")]
pub use linux::smol;

/// An address change event.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
Expand All @@ -50,61 +71,50 @@ pub enum IfEvent {
Down(IpNet),
}

/// Watches for interface changes.
#[derive(Debug)]
pub struct IfWatcher(platform_impl::IfWatcher);

impl IfWatcher {
/// Create a watcher
pub fn new() -> Result<Self> {
platform_impl::IfWatcher::new().map(Self)
}

/// Iterate over current networks.
pub fn iter(&self) -> impl Iterator<Item = &IpNet> {
self.0.iter()
}

/// Poll for an address change event.
pub fn poll_if_event(&mut self, cx: &mut Context) -> Poll<Result<IfEvent>> {
self.0.poll_if_event(cx)
}
}

impl Stream for IfWatcher {
type Item = Result<IfEvent>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::into_inner(self).poll_if_event(cx).map(Some)
}
}

impl FusedStream for IfWatcher {
fn is_terminated(&self) -> bool {
false
}
}

#[cfg(test)]
mod tests {
use super::*;
use futures::StreamExt;
use std::pin::Pin;

#[test]
fn test_ip_watch() {
futures::executor::block_on(async {
fn test_smol_ip_watch() {
use super::smol::IfWatcher;

smol::block_on(async {
let mut set = IfWatcher::new().unwrap();
let event = set.select_next_some().await.unwrap();
println!("Got event {:?}", event);
});
}

#[tokio::test]
async fn test_tokio_ip_watch() {
use super::tokio::IfWatcher;

let mut set = IfWatcher::new().unwrap();
let event = set.select_next_some().await.unwrap();
println!("Got event {:?}", event);
}

#[test]
fn test_is_send() {
futures::executor::block_on(async {
fn test_smol_is_send() {
use super::smol::IfWatcher;

smol::block_on(async {
fn is_send<T: Send>(_: T) {}
is_send(IfWatcher::new());
is_send(IfWatcher::new().unwrap());
is_send(Pin::new(&mut IfWatcher::new().unwrap()));
});
}

#[tokio::test]
async fn test_tokio_is_send() {
use super::tokio::IfWatcher;

fn is_send<T: Send>(_: T) {}
is_send(IfWatcher::new());
is_send(IfWatcher::new().unwrap());
is_send(Pin::new(&mut IfWatcher::new().unwrap()));
}
}
Loading

0 comments on commit 31c4780

Please sign in to comment.