Skip to content

Commit 31dc40e

Browse files
authored
perf: replace ulid with ferroid's ULID for better performance (#829)
Replaces the [ulid](https://github.com/dylanhart/ulid-rs) crate with [ferroid](https://github.com/s0l0ist/ferroid/tree/main/crates/ferroid), a drop-in alternative with identical behavior in this context, but better performance for encode/decode. Also saves an allocation (not that it is critical here). There's no functional difference here, but `ferroid` provides a more modern foundation for ULID.
1 parent 72b35be commit 31dc40e

File tree

5 files changed

+17
-13
lines changed

5 files changed

+17
-13
lines changed

testcontainers/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ tokio = { version = "1", features = ["macros", "fs", "rt-multi-thread"] }
4444
tokio-stream = "0.1.15"
4545
astral-tokio-tar = "0.5.6"
4646
tokio-util = { version = "0.7.10", features = ["io"] }
47-
ulid = { version = "1.1.3" }
47+
ferroid = { version = "0.8.2", features = ["std", "ulid", "base32"] }
4848
url = { version = "2", features = ["serde"] }
4949

5050
[features]

testcontainers/src/core/client.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use bollard::{
2424
},
2525
Docker,
2626
};
27+
use ferroid::{base32::Base32UlidExt, id::ULID};
2728
use futures::{StreamExt, TryStreamExt};
2829
use tokio::sync::{Mutex, OnceCell};
2930
use url::Url;
@@ -466,15 +467,15 @@ impl Client {
466467
.await
467468
.map_err(ClientError::CopyToContainerError)?;
468469

469-
let session = ulid::Ulid::new().to_string();
470+
let session = ULID::from_datetime(std::time::SystemTime::now()).encode();
470471

471472
let mut builder = BuildImageOptionsBuilder::new()
472473
.dockerfile("Dockerfile")
473474
.t(descriptor)
474475
.rm(true)
475476
.nocache(options.no_cache)
476477
.version(BuilderVersion::BuilderBuildKit)
477-
.session(&session);
478+
.session(session.as_str());
478479

479480
if !options.build_args.is_empty() {
480481
builder = builder.buildargs(&options.build_args);

testcontainers/src/core/containers/host.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{convert::TryFrom, future, sync::Arc, time::Duration};
22

3+
use ferroid::{base32::Base32UlidExt, id::ULID};
34
use log::{debug, trace};
45
use russh::{client, Channel, Disconnect};
56
use tokio::{
@@ -8,7 +9,6 @@ use tokio::{
89
time::sleep,
910
};
1011
use tokio_util::sync::CancellationToken;
11-
use ulid::Ulid;
1212
use url::Host as UrlHost;
1313

1414
use super::async_container::ContainerAsync;
@@ -166,7 +166,8 @@ fn prepare_host_exposure<I: Image>(
166166
}
167167
}
168168

169-
let password = format!("tc-{}", Ulid::new());
169+
let suffix = ULID::from_datetime(std::time::SystemTime::now()).encode();
170+
let password = format!("tc-{}", suffix.as_str());
170171

171172
Ok(Some(HostExposurePlan {
172173
requested_ports,

testcontainers/src/runners/async_runner.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use crate::{
2424

2525
const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(60);
2626
#[cfg(feature = "reusable-containers")]
27-
static TESTCONTAINERS_SESSION_ID: std::sync::OnceLock<ulid::Ulid> = std::sync::OnceLock::new();
27+
static TESTCONTAINERS_SESSION_ID: std::sync::OnceLock<ferroid::id::ULID> =
28+
std::sync::OnceLock::new();
2829

2930
#[doc(hidden)]
3031
/// A unique identifier for the currently "active" `testcontainers` "session".
@@ -39,8 +40,9 @@ static TESTCONTAINERS_SESSION_ID: std::sync::OnceLock<ulid::Ulid> = std::sync::O
3940
/// like [`Client::get_running_container_id`](Client::get_running_container_id),
4041
/// as the container name, labels, and network would all still match.
4142
#[cfg(feature = "reusable-containers")]
42-
pub(crate) fn session_id() -> &'static ulid::Ulid {
43-
TESTCONTAINERS_SESSION_ID.get_or_init(ulid::Ulid::new)
43+
pub(crate) fn session_id() -> &'static ferroid::id::ULID {
44+
TESTCONTAINERS_SESSION_ID
45+
.get_or_init(|| ferroid::id::ULID::from_datetime(std::time::SystemTime::now()))
4446
}
4547

4648
#[async_trait]

testcontainers/tests/host_port_exposure.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ use std::{
77
};
88

99
use anyhow::Result;
10+
use ferroid::{base32::Base32UlidExt, id::ULID};
1011
use testcontainers::{
1112
core::{ExecCommand, Host},
1213
runners::AsyncRunner,
1314
ContainerAsync, GenericImage, ImageExt, TestcontainersError,
1415
};
15-
use ulid::Ulid;
1616

1717
const ALPINE_IMAGE: &str = "alpine";
1818
const ALPINE_TAG: &str = "3.19";
@@ -194,10 +194,10 @@ async fn host_port_exposure_is_scoped_per_container() -> Result<()> {
194194
let second_host_port = start_test_http_server("second-host-service")?;
195195

196196
// Generate unique identifiers for network and containers to avoid conflicts
197-
let suffix = Ulid::new().to_string().to_lowercase();
198-
let network_name = format!("tc-host-port-net-{suffix}");
199-
let first_container_name = format!("host-port-first-{suffix}");
200-
let second_container_name = format!("host-port-second-{suffix}");
197+
let suffix = ULID::from_datetime(std::time::SystemTime::now()).encode();
198+
let network_name = format!("tc-host-port-net-{}", suffix.as_str());
199+
let first_container_name = format!("host-port-first-{}", suffix.as_str());
200+
let second_container_name = format!("host-port-second-{}", suffix.as_str());
201201

202202
// Create two containers, each with only one host port exposed
203203
let first_container =

0 commit comments

Comments
 (0)