aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-10-04 18:37:51 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-10-04 18:37:51 -0700
commit3ebb4feb986c3b3aacf33d441645314b587ef5b3 (patch)
tree7e2e2c4511da87acf779fb7042360c34a4f7c909
parente8dab9bfcf4b7822253eaf290cf2f72291b39167 (diff)
downloadbun-3ebb4feb986c3b3aacf33d441645314b587ef5b3.tar.gz
bun-3ebb4feb986c3b3aacf33d441645314b587ef5b3.tar.zst
bun-3ebb4feb986c3b3aacf33d441645314b587ef5b3.zip
Attempt to optimize `bun bun` on Linux by using memfd_create and copying the resulting file via sendfile()
-rw-r--r--src/bundler.zig15
-rw-r--r--src/c.zig65
-rw-r--r--src/fs.zig73
3 files changed, 135 insertions, 18 deletions
diff --git a/src/bundler.zig b/src/bundler.zig
index 472b8bd9f..89c37e9ab 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -703,12 +703,10 @@ pub const Bundler = struct {
std.hash.Wyhash.hash(0, std.mem.span(destination)),
);
- var tmpfile = try tmpdir.createFileZ(tmpname, .{ .read = isDebug, .exclusive = true });
+ var tmpfile = Fs.FileSystem.RealFS.Tmpfile{};
+ try tmpfile.create(&bundler.fs.fs, tmpname);
- errdefer {
- tmpfile.close();
- tmpdir.deleteFile(std.mem.span(tmpname)) catch {};
- }
+ errdefer tmpfile.closeAndDelete(tmpname);
var generator = try allocator.create(GenerateNodeModuleBundle);
var queue = try BunQueue.init(allocator);
@@ -722,7 +720,7 @@ pub const Bundler = struct {
.estimated_input_lines_of_code = 0,
// .resolve_queue = queue,
.bundler = bundler,
- .tmpfile = tmpfile,
+ .tmpfile = tmpfile.file(),
.dynamic_import_file_size_store = U32Map.init(allocator),
.dynamic_import_file_size_store_lock = Lock.init(),
@@ -930,8 +928,7 @@ pub const Bundler = struct {
// }
if (this.log.errors > 0) {
- tmpfile.close();
- tmpdir.deleteFile(std.mem.span(tmpname)) catch {};
+ tmpfile.closeAndDelete(std.mem.span(tmpname));
// We stop here because if there are errors we don't know if the bundle is valid
// This manifests as a crash when sorting through the module list because we may have added files to the bundle which were never actually finished being added.
return null;
@@ -1112,7 +1109,7 @@ pub const Bundler = struct {
// chmod 777
0000010 | 0000100 | 0000001 | 0001000 | 0000040 | 0000004 | 0000002 | 0000400 | 0000200 | 0000020,
);
- try C.moveFileZ(tmpdir.fd, tmpname, top_dir.fd, destination);
+ try tmpfile.promote(tmpname, top_dir.fd, destination);
// Print any errors at the end
// try this.log.print(Output.errorWriter());
return javascript_bundle_container;
diff --git a/src/c.zig b/src/c.zig
index 9e07f3082..ad7631709 100644
--- a/src/c.zig
+++ b/src/c.zig
@@ -17,10 +17,10 @@ const errno = os.errno;
const zeroes = mem.zeroes;
pub extern "c" fn chmod([*c]const u8, mode_t) c_int;
-pub extern "c" fn fchmod(c_int, mode_t) c_int;
+pub extern "c" fn fchmod(std.c.fd_t, mode_t) c_int;
pub extern "c" fn umask(mode_t) mode_t;
pub extern "c" fn fchmodat(c_int, [*c]const u8, mode_t, c_int) c_int;
-
+pub extern "c" fn fchown(std.c.fd_t, std.c.uid_t, std.c.gid_t) c_int;
pub extern "c" fn lstat([*c]const u8, [*c]libc_stat) c_int;
pub extern "c" fn lstat64([*c]const u8, [*c]libc_stat) c_int;
@@ -106,14 +106,61 @@ pub fn moveFileZ(from_dir: std.os.fd_t, filename: [*:0]const u8, to_dir: std.os.
};
}
+pub fn moveFileZWithHandle(from_handle: std.os.fd_t, from_dir: std.os.fd_t, filename: [*:0]const u8, to_dir: std.os.fd_t, destination: [*:0]const u8) !void {
+ std.os.renameatZ(from_dir, filename, to_dir, destination) catch |err| {
+ switch (err) {
+ error.RenameAcrossMountPoints => {
+ try moveFileZSlowWithHandle(from_handle, to_dir, destination);
+ },
+ else => {
+ return err;
+ },
+ }
+ };
+}
+
// On Linux, this will be fast because sendfile() supports copying between two file descriptors on disk
-// On macOS & BSDs, this will be slow because it will attempt to copy with sendfile, fail, and then fallback to a copy loop
+// macOS & BSDs will be slow because
pub fn moveFileZSlow(from_dir: std.os.fd_t, filename: [*:0]const u8, to_dir: std.os.fd_t, destination: [*:0]const u8) !void {
- const flags = std.os.O_RDWR;
- const in_handle = try std.os.openatZ(from_dir, filename, flags, 0777);
- defer std.os.close(in_handle);
- const out_handle = try std.os.openatZ(to_dir, filename, flags | std.os.O_CREAT, 0777);
+ const in_handle = try std.os.openatZ(from_dir, filename, std.os.O_RDONLY | std.os.O_CLOEXEC, 0600);
+ try moveFileZSlowWithHandle(in_handle, to_dir, destination);
+}
+
+pub fn moveFileZSlowWithHandle(in_handle: std.os.fd_t, to_dir: std.os.fd_t, destination: [*:0]const u8) !void {
+ const stat = try std.os.fstat(in_handle);
+ // delete if exists, don't care if it fails. it may fail due to the file not existing
+ // delete here because we run into weird truncation issues if we do not
+ // ftruncate() instead didn't work.
+ // this is technically racy because it could end up deleting the file without saving
+ std.os.unlinkatZ(to_dir, destination, 0) catch {};
+ const out_handle = try std.os.openatZ(to_dir, destination, std.os.O_WRONLY | std.os.O_CREAT | std.os.O_CLOEXEC, 022);
defer std.os.close(out_handle);
- const written = try std.os.sendfile(out_handle, in_handle, 0, 0, &[_]std.c.iovec_const{}, &[_]std.c.iovec_const{}, 0);
- try std.os.ftruncate(out_handle, written);
+ if (comptime Enviroment.isLinux) {
+ std.os.system.fallocate(out_handle, 0, 0, @intCast(i64, stat.size));
+ _ = try std.os.sendfile(out_handle, in_handle, 0, @intCast(usize, stat.size), &[_]std.c.iovec_const{}, &[_]std.c.iovec_const{}, 0);
+ } else {
+ if (comptime Enviroment.isMac) {
+ // if this fails, it doesn't matter
+ // we only really care about read & write succeeding
+ preallocate_file(
+ out_handle,
+ @intCast(std.os.off_t, 0),
+ @intCast(std.os.off_t, stat.size),
+ ) catch {};
+ }
+
+ var buf: [8092 * 2]u8 = undefined;
+ var total_read: usize = 0;
+ while (true) {
+ const read = try std.os.pread(in_handle, &buf, total_read);
+ total_read += read;
+ if (read == 0) break;
+ const bytes = buf[0..read];
+ const written = try std.os.write(out_handle, bytes);
+ if (written == 0) break;
+ }
+ }
+
+ _ = fchmod(out_handle, stat.mode);
+ _ = fchown(out_handle, stat.uid, stat.gid);
}
diff --git a/src/fs.zig b/src/fs.zig
index 8a6d4e5f7..434f4ce36 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -513,6 +513,79 @@ pub const FileSystem = struct {
return file;
}
+ pub const Tmpfile = struct {
+ fd: std.os.fd_t = 0,
+ dir_fd: std.os.fd_t = 0,
+
+ pub inline fn dir(this: *Tmpfile) std.fs.Dir {
+ return std.fs.Dir{
+ .fd = this.dir_fd,
+ };
+ }
+
+ pub inline fn file(this: *Tmpfile) std.fs.File {
+ return std.fs.File{
+ .handle = this.fd,
+ };
+ }
+
+ pub fn close(this: *Tmpfile) void {
+ if (this.fd != 0) std.os.close(this.fd);
+ }
+
+ pub fn create(this: *Tmpfile, rfs: *RealFS, name: [*:0]const u8) !void {
+ if (comptime Environment.isLinux) {
+ this.fd = try std.os.memfd_createZ(name, std.os.MFD_CLOEXEC);
+ return;
+ }
+
+ var tmpdir_ = try rfs.openTmpDir();
+ this.dir_fd = tmpdir_.fd;
+ this.fd = try std.os.openatZ(tmpdir_.fd, name, std.os.O_CREAT | std.os.O_EXCL | std.os.O_RDWR | std.os.O_CLOEXEC, 0666);
+ }
+
+ pub fn promoteLinux(this: *Tmpfile, destination_fd: std.os.fd_t, name: [*:0]const u8) !void {
+ std.debug.assert(this.fd != 0);
+
+ const out_handle = try std.os.openatZ(destination_fd, name, std.os.O_WRONLY | std.os.O_CREAT | std.os.O_CLOEXEC, 022);
+ defer std.os.close(out_handle);
+ const read = try std.os.sendfile(
+ out_handle,
+ this.fd,
+ 0,
+ 0,
+ &[_]std.c.iovec_const{},
+ &[_]std.c.iovec_const{},
+ 0,
+ );
+ try std.os.ftruncate(out_handle, read);
+ this.close();
+ }
+
+ pub fn promote(this: *Tmpfile, from_name: [*:0]const u8, destination_fd: std.os.fd_t, name: [*:0]const u8) !void {
+ if (comptime Environment.isLinux) {
+ try @call(.{ .modifier = .always_inline }, promoteLinux, .{ this, destination_fd, name });
+ return;
+ }
+
+ std.debug.assert(this.fd != 0);
+ std.debug.assert(this.dir_fd != 0);
+
+ try C.moveFileZWithHandle(this.fd, this.dir_fd, from_name, destination_fd, name);
+ this.close();
+ }
+
+ pub fn closeAndDelete(this: *Tmpfile, name: [*:0]const u8) void {
+ this.close();
+
+ if (comptime !Environment.isLinux) {
+ if (this.dir_fd == 0) return;
+
+ this.dir().deleteFileZ(name) catch {};
+ }
+ }
+ };
+
inline fn _fetchCacheFile(fs: *RealFS, basename: string) !std.fs.File {
var parts = [_]string{ "node_modules", ".cache", basename };
var path = fs.parent_fs.join(&parts);