Skip to content
Open
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
6 changes: 0 additions & 6 deletions Cargo.lock

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

49 changes: 49 additions & 0 deletions bench/snippets/filesystem-router.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { bench, run } from "../runner.mjs";

const root = mkdtempSync(join(tmpdir(), "bun-bench-fsr-"));
process.on("exit", () => rmSync(root, { recursive: true, force: true }));

// 500 routes in 10 directories, one in ten of them dynamic.
const dense = join(root, "dense");
for (let d = 0; d < 10; d++) {
const section = d === 0 ? dense : join(dense, `section-${d}`);
mkdirSync(section, { recursive: true });
for (let i = 0; i < 50; i++) {
writeFileSync(join(section, i % 10 === 0 ? `[param${i}].tsx` : `page-${i}.tsx`), "");
}
}

// 50 routes in 50 directories: the shape in which a scan costs the most for each route.
const sparse = join(root, "sparse");
// The same, with each route file a link to a file outside the tree.
const linked = join(root, "linked");
mkdirSync(join(root, "targets"));
for (let d = 0; d < 50; d++) {
mkdirSync(join(sparse, `section-${d}`), { recursive: true });
writeFileSync(join(sparse, `section-${d}`, "page.tsx"), "");
mkdirSync(join(linked, `section-${d}`), { recursive: true });
writeFileSync(join(root, "targets", `page-${d}.tsx`), "");
symlinkSync(join(root, "targets", `page-${d}.tsx`), join(linked, `section-${d}`, "page.tsx"));
}

// The first scan of a process is not a repeat of anything: time it once, on its own.
const start = Bun.nanoseconds();
const router = new Bun.FileSystemRouter({ style: "nextjs", dir: dense });
console.log(`first new Bun.FileSystemRouter(): ${((Bun.nanoseconds() - start) / 1e6).toFixed(2)} ms`);

for (const [shape, dir] of [
["500 routes, 10 directories", dense],
["50 routes, 50 directories", sparse],
["50 linked routes, 50 directories", linked],
]) {
const reloaded = new Bun.FileSystemRouter({ style: "nextjs", dir });
bench(`new Bun.FileSystemRouter() (${shape})`, () => new Bun.FileSystemRouter({ style: "nextjs", dir }));
bench(`router.reload() (${shape})`, () => reloaded.reload());
}
bench("router.match() a static route", () => router.match("/section-3/page-7"));
bench("router.match() a dynamic route", () => router.match("/section-3/hello"));

await run();
1 change: 1 addition & 0 deletions scripts/rust-miri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const MIRI_CRATES = [
"bun_pod",
"bun_ptr",
"bun_resolve_builtins",
"bun_router",
"bun_semver",
"bun_shell_parser",
"bun_threading",
Expand Down
48 changes: 0 additions & 48 deletions src/bun_alloc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,6 @@ pub fn trim<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] {
}

// ─── ascii-lowercase helpers ──────────────────────────────────────────────
// Sunk from bun_core::strings so bun_alloc::BSSList::append_lower_case can call
// them without a dep cycle (bun_core → bun_alloc, not the reverse).
// `bun_core::strings` re-exports `copy_lowercase` and `ascii_lowercase_buf`.

/// ASCII-lowercase
Expand Down Expand Up @@ -2033,52 +2031,6 @@ impl<const COUNT: usize, const ITEM_LENGTH: usize> BSSStringList<COUNT, ITEM_LEN
Ok(unsafe { core::slice::from_raw_parts(ptr, len) })
}

/// Append `value` lowercased ASCII-wise.
///
/// The previous port routed the lowercase scratch through a
/// `thread_local! { RefCell<Box<[u8; 4096]>> }`, which (a) heap-allocs 4 KiB
/// on first use per thread and (b) pays a `RefCell` flag check per call.
/// Filenames are overwhelmingly <256 bytes, so a stack scratch suffices for
/// the hot path; longer inputs (rare — full paths) fall through to a
/// one-shot heap temp. No TLS, no Box-on-first-use, no `RefCell`.
///
/// SAFETY: see [`append`].
pub unsafe fn append_lower_case<'a>(
this: *mut Self,
value: &[u8],
) -> core::result::Result<&'a [u8], AllocError> {
// SAFETY: see `append`.
let _guard = unsafe { (*this).mutex.lock() };

// `do_append` only reads `slice` via `BSSAppendable::copy_into` (copies
// into `self.backing_buf` / a fresh heap alloc) and returns raw parts
// pointing at that owned storage, not at `slice` — so the scratch
// buffer's borrow does not escape.
let (ptr, len) = if value.len() <= 256 {
let mut scratch = [0u8; 256];
// SAFETY: inner mutex held ⇒ this thread has exclusive access.
unsafe {
(*this).do_append(&crate::copy_lowercase(value, &mut scratch[..value.len()]))?
}
} else {
// Slow path: input >256 bytes (rare). Use a one-shot heap temp via
// mimalloc directly (PORTING.md forbids `Vec` in hot allocators).
let p = mimalloc::mi_malloc(value.len()).cast::<u8>();
if p.is_null() {
return Err(AllocError);
}
// SAFETY: `p` is a fresh allocation of `value.len()` bytes; sole owner.
let tmp = unsafe { core::slice::from_raw_parts_mut(p, value.len()) };
// SAFETY: inner mutex held ⇒ this thread has exclusive access.
let r = unsafe { (*this).do_append(&crate::copy_lowercase(value, tmp)) };
// SAFETY: `p` was allocated by `mi_malloc` above.
unsafe { mimalloc::mi_free(p.cast()) };
r?
};
// SAFETY: see `append`.
Ok(unsafe { core::slice::from_raw_parts(ptr, len) })
}

/// Returns `(ptr, len)` of the freshly-appended payload (excluding the trailing NUL),
/// where `ptr` carries write provenance (`out.as_mut_ptr()`). Callers reconstruct a
/// `&[u8]` (`append`) or `&mut [u8]` (`append_mutable`) from it; returning raw parts
Expand Down
2 changes: 1 addition & 1 deletion src/install/NetworkTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{ExtractTarball, PackageManager, TarballStream, Task};

// Adapter so `StringOrTinyString::init_append_if_needed` can intern overflow
// names into the resolver's filename arena. The bun_sys-level `FilenameStore` exposes `append` /
// `append_lower_case` but doesn't itself implement `strings::Appender` (that
// `append_slice` but doesn't itself implement `strings::Appender` (that
// impl lives in `bun_resolver`, which this crate can't reach without a cycle).
pub struct FilenameStoreAppender<'a>(pub(crate) &'a FilenameStore);
impl strings::Appender for FilenameStoreAppender<'_> {
Expand Down
20 changes: 20 additions & 0 deletions src/jsc/hot_reloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ impl ImportWatcher {
.items::<"package_json", Option<&'static bun_watcher::PackageJSON>>()[index as usize]
}

/// The paths of the watched files whose directory `in_dir` accepts, copied under the watcher's mutex.
pub fn watched_files(&self, mut in_dir: impl FnMut(&[u8]) -> bool) -> Vec<Box<[u8]>> {
let w = match self {
ImportWatcher::Hot(w) | ImportWatcher::Watch(w) => w,
ImportWatcher::None => return Vec::new(),
};
let _guard = w.mutex.lock_guard();
let paths = w.watchlist.items_file_path();
let kinds = w.watchlist.items_kind();
paths
.iter()
.zip(kinds)
.filter(|(path, kind)| {
**kind == bun_watcher::Kind::File
&& in_dir(bun_paths::dirname(path).unwrap_or_default())
})
.map(|(path, _)| Box::from(&**path))
.collect()
}

#[inline]
pub fn add_file_by_path_slow(&mut self, file_path: &[u8]) -> bool {
match self {
Expand Down
6 changes: 0 additions & 6 deletions src/resolver/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,6 @@ impl Entry {
self.base_lowercase_.slice()
}

/// Interned in DirnameStore.
#[inline]
pub fn dir(&self) -> &'static [u8] {
self.dir
}

/// Returns `Interned::EMPTY` until a fill has been published. Lock-free.
#[inline]
pub fn abs_path(&self) -> Interned {
Expand Down
11 changes: 0 additions & 11 deletions src/resolver/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,6 @@ pub mod fs {
unsafe { bun_alloc::BSSStringList::append($backing(), &value) }
.map_err(|_| bun_alloc::AllocError)
}
/// Like `append`, but ASCII-lowercases `value` while copying
/// it into the store.
#[inline]
pub fn append_lower_case(
&self,
value: &[u8],
) -> core::result::Result<&'static [u8], bun_alloc::AllocError> {
// SAFETY: see `append`.
unsafe { bun_alloc::BSSStringList::append_lower_case($backing(), value) }
.map_err(|_| bun_alloc::AllocError)
}
}
};
}
Expand Down
9 changes: 0 additions & 9 deletions src/router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,13 @@ path = "lib.rs"
workspace = true

[dependencies]
thiserror.workspace = true
bun_errno.workspace = true
bun_alloc.workspace = true
strum.workspace = true
bstr.workspace = true
scopeguard.workspace = true
bun_core.workspace = true
bun_collections.workspace = true
bun_ast.workspace = true
bun_http_types.workspace = true
bun_paths.workspace = true
bun_ptr.workspace = true
# `bun_resolver` is acyclic from this crate (resolver does not depend on
# router); router consumes the real `bun_resolver::fs::{FileSystem,DirEntry}`
# now that the `bun_sys::fs::DirEntry` opaque-seam workaround is gone.
bun_resolver.workspace = true
bun_sys.workspace = true
bun_url.workspace = true
bun_wyhash.workspace = true
25 changes: 0 additions & 25 deletions src/router/error.rs

This file was deleted.

Loading
Loading