Linux works now.

This commit is contained in:
Jarred SUmner
2021-10-02 22:54:19 -07:00
parent ac7a96b088
commit cdabcfd9d0
21 changed files with 322 additions and 102 deletions

14
.dockerignore Normal file
View File

@@ -0,0 +1,14 @@
node_modules
**/node_modules
src/javascript/jsc/WebKit/LayoutTests
zig-out
zig-build
**/*.o
**/*.a
examples
**/.next
.git
src/javascript/jsc/WebKit/WebKitBuild
**/CMakeCache.txt

9
.vscode/launch.json vendored
View File

@@ -79,6 +79,15 @@
"cwd": "${workspaceFolder}/src/test/fixtures",
"console": "internalConsole"
},
{
"type": "lldb",
"request": "launch",
"name": "Linux Launch",
"program": "${workspaceFolder}/packages/debug-bun-cli-linux-x64/bin/bun-debug",
"args": ["--origin=http://jarred-desktop.local:3000/"],
"cwd": "${workspaceFolder}/examples/hello-next",
"console": "internalConsole"
},
{
"type": "lldb",

8
Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM bun-zig:latest
COPY . /home/ubuntu/bun
WORKDIR /home/ubuntu/bun
RUN make vendor-without-check

52
Dockerfile.zig Normal file
View File

@@ -0,0 +1,52 @@
FROM ubuntu:latest
RUN apt-get update && apt-get install --no-install-recommends -y wget gnupg2 curl lsb-release wget software-properties-common
RUN curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
RUN wget https://apt.llvm.org/llvm.sh --no-check-certificate
RUN chmod +x llvm.sh
RUN ./llvm.sh 12
RUN apt-get update && apt-get install --no-install-recommends -y \
ca-certificates \
curl \
gnupg2 \
software-properties-common \
cmake \
build-essential \
git \
libssl-dev \
ruby \
liblld-12-dev \
libclang-12-dev \
nodejs \
gcc \
g++ \
npm \
clang-12 \
clang-format-12 \
libc++-12-dev \
libc++abi-12-dev \
lld-12 \
libicu-dev
RUN update-alternatives --install /usr/bin/ld ld /usr/bin/lld-12 90 && \
update-alternatives --install /usr/bin/cc cc /usr/bin/clang-12 90 && \
update-alternatives --install /usr/bin/cpp cpp /usr/bin/clang++-12 90 && \
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-12 90
ENV CC=clang-12
ENV CXX=clang++-12
# Compile zig
RUN mkdir -p /home/ubuntu/zig; cd /home/ubuntu; git clone https://github.com/jarred-sumner/zig.git; cd /home/ubuntu/zig && git checkout jarred/zig-sloppy-with-small-structs && cmake . -DCMAKE_BUILD_TYPE=Release && make -j$(nproc)
ENV PATH="/home/ubuntu/zig:$PATH"
RUN npm install -g esbuild

View File

@@ -24,7 +24,10 @@ CXX := clang++
bun: vendor build-obj bun-link-lld-release
vendor: require init-submodules api node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp jsc
vendor-without-check: api node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp
vendor: require init-submodules vendor-without-check
require:
@echo "Checking if the required utilities are available..."
@@ -67,6 +70,30 @@ runtime_js:
bun_error:
@cd packages/bun-error; npm install; npm run --silent build
DEFAULT_USE_BMALLOC := 1
# ifeq ($(OS_NAME),linux)
# DEFAULT_USE_BMALLOC = 0
# endif
USE_BMALLOC ?= DEFAULT_USE_BMALLOC
DEFAULT_JSC_LIB :=
ifeq ($(OS_NAME),linux)
DEFAULT_JSC_LIB = ${HOME}/webkit-build/lib
endif
ifeq ($(OS_NAME),darwin)
DEFAULT_JSC_LIB = src/deps
endif
JSC_LIB ?= $(DEFAULT_JSC_LIB)
JSC_INCLUDE_DIR ?= ${HOME}/webkit-build/include
JSC_FILES := $(JSC_LIB)/libJavaScriptCore.a $(JSC_LIB)/libWTF.a
JSC_BUILD_STEPS :=
ifeq ($(OS_NAME),linux)
JSC_BUILD_STEPS += jsc-build-linux jsc-copy-headers
@@ -75,6 +102,10 @@ ifeq ($(OS_NAME),darwin)
JSC_BUILD_STEPS += jsc-build-mac jsc-copy-headers
endif
# ifeq ($(USE_BMALLOC),1)
JSC_FILES += $(JSC_LIB)/libbmalloc.a
# endif
jsc: jsc-build jsc-bindings
jsc-build: $(JSC_BUILD_STEPS)
@@ -177,10 +208,12 @@ jsc-build-mac-copy:
cp src/javascript/jsc/WebKit/WebKitBuild/Release/lib/libWTF.a src/deps/libWTF.a
cp src/javascript/jsc/WebKit/WebKitBuild/Release/lib/libbmalloc.a src/deps/libbmalloc.a
JSC_FILES := src/deps/libJavaScriptCore.a \
src/deps/libWTF.a \
src/deps/libbmalloc.a
clean-bindings:
rm -rf $(OBJ_DIR)/*.o
clean: clean-bindings
rm src/deps/*.a src/deps/*.o
cd src/deps/mimalloc && make clean;
ifeq ($(OS_NAME),darwin)
HOMEBREW_PREFIX := $(shell brew --prefix)/
@@ -190,18 +223,31 @@ SRC_DIR := src/javascript/jsc/bindings
OBJ_DIR := src/javascript/jsc/bindings-obj
SRC_FILES := $(wildcard $(SRC_DIR)/*.cpp)
OBJ_FILES := $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRC_FILES))
INCLUDE_DIRS := -Isrc/javascript/jsc/WebKit/WebKitBuild/Release/JavaScriptCore/PrivateHeaders \
MAC_INCLUDE_DIRS := -Isrc/javascript/jsc/WebKit/WebKitBuild/Release/JavaScriptCore/PrivateHeaders \
-Isrc/javascript/jsc/WebKit/WebKitBuild/Release/WTF/Headers \
-Isrc/javascript/jsc/WebKit/WebKitBuild/Release/ICU/Headers \
-Isrc/javascript/jsc/WebKit/WebKitBuild/Release/ \
-Isrc/javascript/jsc/bindings/ \
-Isrc/javascript/jsc/WebKit/Source/bmalloc
LINUX_INCLUDE_DIRS := -I$(JSC_INCLUDE_DIR) \
-Isrc/javascript/jsc/bindings/
INCLUDE_DIRS :=
ifeq ($(OS_NAME),linux)
INCLUDE_DIRS += $(LINUX_INCLUDE_DIRS)
endif
ifeq ($(OS_NAME),darwin)
INCLUDE_DIRS += $(MAC_INCLUDE_DIRS)
endif
CLANG_FLAGS := $(INCLUDE_DIRS) \
-std=gnu++17 \
-stdlib=libc++ \
-DSTATICALLY_LINKED_WITH_JavaScriptCore=1 \
-DSTATICALLY_LINKED_WITH_WTF=1 \
-DSTATICALLY_LINKED_WITH_BMALLOC=1 \
-DBUILDING_WITH_CMAKE=1 \
-DNDEBUG=1 \
-DNOMINMAX \
@@ -209,8 +255,15 @@ CLANG_FLAGS := $(INCLUDE_DIRS) \
-g \
-DENABLE_INSPECTOR_ALTERNATE_DISPATCHERS=0 \
-DBUILDING_JSCONLY__ \
-DASSERT_ENABLED=0\
-DDU_DISABLE_RENAMING=1
-DASSERT_ENABLED=0 \
-fPIE
# This flag is only added to webkit builds on Apple platforms
# It has something to do with ICU
ifeq ($(OS_NAME), darwin)
CLANG_FLAGS += -DDU_DISABLE_RENAMING=1
endif
jsc-bindings-mac: $(OBJ_FILES)
@@ -239,7 +292,20 @@ BUN_LLD_FLAGS := $(OBJ_FILES) \
src/deps/picohttpparser.o \
src/deps/mimalloc/libmimalloc.a \
$(CLANG_FLAGS) \
-fpie \
ifeq ($(OS_NAME), linux)
BUN_LLD_FLAGS += -lstdc++fs \
-pthread \
-ldl \
-lc \
-fuse-ld=lld \
-Wl,-z,now \
-Wl,--as-needed \
-Wl,-z,stack-size=12800000 \
-Wl,-z,notext
endif
mimalloc:
cd src/deps/mimalloc; cmake .; make;
@@ -248,17 +314,19 @@ bun-link-lld-debug:
$(CXX) $(BUN_LLD_FLAGS) \
$(DEBUG_BIN)/bun-debug.o \
-W \
-ftls-model=local-exec \
-flto \
-o $(DEBUG_BIN)/bun-debug
-o $(DEBUG_BIN)/bun-debug \
-march=native \
-flto
bun-link-lld-release:
$(CXX) $(BUN_LLD_FLAGS) \
$(BIN_DIR)/bun.o \
-o $(BIN_DIR)/bun \
-W \
-ftls-model=local-exec \
-flto \
-ftls-model=initial-exec \
-march=native \
-O3
rm $(BIN_DIR)/bun.o
@@ -267,7 +335,7 @@ bun-link-lld-release-aarch64:
build/macos-aarch64/bun.o \
-o build/macos-aarch64/bun \
-Wl,-dead_strip \
-ftls-model=local-exec \
-ftls-model=initial-exec \
-flto \
-O3
@@ -283,4 +351,4 @@ sizegen:
/tmp/sizegen > src/javascript/jsc/bindings/sizes.zig
picohttp:
$(CC) -O3 -g -c src/deps/picohttpparser.c -Isrc/deps -o src/deps/picohttpparser.o; cd ../../
$(CC) -O3 -g -fPIE -c src/deps/picohttpparser.c -Isrc/deps -o src/deps/picohttpparser.o; cd ../../

View File

@@ -600,7 +600,4 @@ Additionally, you'll need `cmake`, `npm` and `esbuild` installed globally.
## Linux
```bash
git submodule update --init --recursive --progress --depth=1
make vendor
```
Compile Zig:

View File

@@ -237,9 +237,13 @@ pub fn build(b: *std.build.Builder) !void {
true,
);
step.addObjectFile("src/deps/libJavaScriptCore.a");
step.addObjectFile("src/deps/libWTF.a");
step.addObjectFile("src/deps/libbmalloc.a");
if (target.getOs().tag != .linux) {
step.addObjectFile("src/deps/libbmalloc.a");
}
step.addObjectFile("src/deps/mimalloc/libmimalloc.a");
step.addLibPath("src/deps/mimalloc");

View File

@@ -5,12 +5,11 @@ import styles from "../styles/Home.module.css";
export async function getStaticProps(ctx) {
return {
props: {
},
props: {},
};
}
export default function Home({ }) {
export default function Home({}) {
return (
<div className={styles.container}>
<Head>
@@ -21,7 +20,7 @@ export default function Home({ }) {
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
Welcome!!to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>

View File

@@ -1,6 +1,6 @@
@import url("./2.css");
.container {
min-height: 100vh;
min-height: 99vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
@@ -8,6 +8,8 @@
align-items: center;
height: 100vh;
}
.main {
padding: 5rem 0;
flex: 1;

View File

@@ -1,7 +1,7 @@
{
"dependencies": {
"moment": "^2.29.1",
"peechy": "^0.4.18",
"peechy": "^0.4.19",
"puppeteer": "^10.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"

View File

@@ -2920,7 +2920,7 @@ pub const Transformer = struct {
var arena: std.heap.ArenaAllocator = undefined;
const use_arenas = opts.entry_points.len > 8;
var ulimit: usize = Fs.FileSystem.RealFS.adjustUlimit();
var ulimit: usize = Fs.FileSystem.RealFS.adjustUlimit() catch unreachable;
var care_about_closing_files = !(FeatureFlags.store_file_descriptors and opts.entry_points.len * 2 < ulimit);
var transformer = Transformer{

View File

@@ -1263,7 +1263,12 @@ pub fn NewBundler(
if (watcher_index == null) {
var file = try std.fs.openFileAbsolute(absolute_path, .{ .read = true });
try this.watcher.appendFile(file.handle, absolute_path, hash, .css, 0, null, true);
if (this.watcher.watchloop_handle == null) {
try this.watcher.start();
}
}
try this.import_queue.writeItem(hash);

View File

@@ -541,16 +541,22 @@ pub const FileSystem = struct {
}
// Always try to max out how many files we can keep open
pub fn adjustUlimit() usize {
var limit = std.os.getrlimit(.NOFILE) catch return 32;
if (limit.cur < limit.max) {
var new_limit = std.mem.zeroes(std.os.rlimit);
new_limit.cur = limit.max;
new_limit.max = limit.max;
std.os.setrlimit(.NOFILE, new_limit) catch return limit.cur;
return new_limit.cur;
pub fn adjustUlimit() !usize {
const LIMITS = [_]std.os.rlimit_resource{std.os.rlimit_resource.STACK, std.os.rlimit_resource.NOFILE};
inline for (LIMITS) |limit_type, i| {
const limit = try std.os.getrlimit(limit_type);
if (limit.cur < limit.max) {
var new_limit = std.mem.zeroes(std.os.rlimit);
new_limit.cur = limit.max;
new_limit.max = limit.max;
try std.os.setrlimit(limit_type, new_limit);
}
if (i == LIMITS.len - 1) return limit.max;
}
return limit.cur;
}
var _entries_option_map: *EntriesOption.Map = undefined;
@@ -559,7 +565,7 @@ pub const FileSystem = struct {
allocator: *std.mem.Allocator,
cwd: string,
) RealFS {
const file_limit = adjustUlimit();
const file_limit = adjustUlimit() catch unreachable;
if (!_entries_option_map_loaded) {
_entries_option_map = EntriesOption.Map.init(allocator);

View File

@@ -1,10 +1,15 @@
const std = @import("std");
pub usingnamespace @import("strings.zig");
pub const Environment = @import("env.zig");
pub const default_allocator: *std.mem.Allocator = if (isTest) std.heap.c_allocator else @import("./memory_allocator.zig").c_allocator;
pub const default_allocator: *std.mem.Allocator = if (isTest or Environment.isLinux)
std.heap.c_allocator
else
@import("./memory_allocator.zig").c_allocator;
pub const C = @import("c.zig");
pub const Environment = @import("env.zig");
pub usingnamespace Environment;
pub const FeatureFlags = @import("feature_flags.zig");

View File

@@ -50,6 +50,7 @@ threadlocal var req_headers_buf: [100]picohttp.Header = undefined;
threadlocal var res_headers_buf: [100]picohttp.Header = undefined;
const sync = @import("./sync.zig");
const JavaScript = @import("./javascript/jsc/javascript.zig");
const JavaScriptCore = @import("./javascript/jsc/JavascriptCore.zig");
usingnamespace @import("./javascript/jsc/bindings/bindings.zig");
usingnamespace @import("./javascript/jsc/bindings/exports.zig");
const Router = @import("./router.zig");
@@ -1102,11 +1103,11 @@ pub const RequestContext = struct {
pub var channel: Channel = undefined;
var has_loaded_channel = false;
pub var javascript_disabled = false;
var js_thread: std.Thread = undefined;
pub fn spawnThread(handler: *HandlerThread) !void {
var thread = try std.Thread.spawn(.{}, spawn, .{handler});
thread.setName("WebSocket") catch {};
thread.detach();
js_thread = try std.Thread.spawn(.{.stack_size = 64 * 1024 * 1024}, spawn, .{handler});
js_thread.setName("JavaScript SSR") catch {};
js_thread.detach();
}
pub fn spawn(handler: *HandlerThread) void {
@@ -1118,20 +1119,19 @@ pub const RequestContext = struct {
javascript_disabled = true;
}
var start_timer = std.time.Timer.start() catch unreachable;
var stdout = std.io.getStdOut();
// var stdout = std.io.bufferedWriter(stdout_file.writer());
var stderr = std.io.getStdErr();
// var stderr = std.io.bufferedWriter(stderr_file.writer());
var output_source = Output.Source.init(stdout, stderr);
// defer stdout.flush() catch {};
// defer stderr.flush() catch {};
defer Output.flush();
Output.Source.set(&output_source);
js_ast.Stmt.Data.Store.create(std.heap.c_allocator);
js_ast.Expr.Data.Store.create(std.heap.c_allocator);
defer Output.flush();
var vm = JavaScript.VirtualMachine.init(
std.heap.c_allocator,
handler.args,
@@ -1947,6 +1947,8 @@ pub const RequestContext = struct {
// CSS handles this specially
if (loader != .css and client_entry_point_ == null) {
if (written.input_fd) |written_fd| {
try ctx.watcher.addFile(
written_fd,
result.file.input.text,
@@ -1958,8 +1960,9 @@ pub const RequestContext = struct {
);
if (ctx.watcher.watchloop_handle == null) {
try ctx.watcher.start();
ctx.watcher.start() catch {};
}
}
} else {
if (written.written > 0) {
@@ -2008,6 +2011,7 @@ pub const RequestContext = struct {
if (file.autowatch) {
// we must never autowatch a file that will be closed
std.debug.assert(!file.close_handle_on_complete);
if (ctx.watcher.addFile(
file.fd,
result.file.input.text,
@@ -2438,16 +2442,6 @@ pub const Server = struct {
transform_options: Api.TransformOptions,
javascript_enabled: bool = false,
pub fn adjustUlimit() !void {
var limit = try std.os.getrlimit(.NOFILE);
if (limit.cur < limit.max) {
var new_limit = std.mem.zeroes(std.os.rlimit);
new_limit.cur = limit.max;
new_limit.max = limit.max;
try std.os.setrlimit(.NOFILE, new_limit);
}
}
pub fn onTCPConnection(server: *Server, conn: tcp.Connection, comptime features: ConnectionFeatures) void {
conn.client.setNoDelay(true) catch {};
conn.client.setQuickACK(true) catch {};
@@ -2502,6 +2496,9 @@ pub const Server = struct {
defer ctx.watcher.flushEvictions();
defer Output.flush();
var rfs: *Fs.FileSystem.RealFS = &ctx.bundler.fs.fs;
// It's important that this function does not do any memory allocations
// If this blocks, it can cause cascading bad things to happen
for (events) |event| {
@@ -2526,7 +2523,6 @@ pub const Server = struct {
switch (kind) {
.file => {
if (event.op.delete or event.op.rename) {
var rfs: *Fs.FileSystem.RealFS = &ctx.bundler.fs.fs;
ctx.watcher.removeAtIndex(
event.index,
0,
@@ -2559,12 +2555,12 @@ pub const Server = struct {
}
},
.directory => {
var rfs: *Fs.FileSystem.RealFS = &ctx.bundler.fs.fs;
rfs.bustEntriesCache(file_path);
ctx.bundler.resolver.dir_cache.remove(file_path);
if (event.op.delete or event.op.rename)
ctx.watcher.removeAtIndex(event.index, hashes[event.index], parent_hashes, .directory);
// if (event.op.delete or event.op.rename)
// ctx.watcher.removeAtIndex(event.index, hashes[event.index], parent_hashes, .directory);
if (comptime is_emoji_enabled) {
Output.prettyln("<r>📁 <d>Dir change: {s}<r>", .{ctx.bundler.fs.relativeTo(file_path)});
@@ -2577,7 +2573,7 @@ pub const Server = struct {
}
fn run(server: *Server, comptime features: ConnectionFeatures) !void {
adjustUlimit() catch {};
_ = Fs.FileSystem.RealFS.adjustUlimit() catch {};
RequestContext.WebsocketHandler.open_websockets = @TypeOf(
RequestContext.WebsocketHandler.open_websockets,
).init(server.allocator);
@@ -2723,6 +2719,7 @@ pub const Server = struct {
};
};
threadlocal var req_ctx_: RequestContext = undefined;
pub fn handleConnection(server: *Server, conn: *tcp.Connection, comptime features: ConnectionFeatures) void {
// https://stackoverflow.com/questions/686217/maximum-on-http-header-values
@@ -2745,9 +2742,9 @@ pub const Server = struct {
var request_arena = server.allocator.create(std.heap.ArenaAllocator) catch unreachable;
request_arena.* = std.heap.ArenaAllocator.init(server.allocator);
var req_ctx: RequestContext = undefined;
req_ctx = RequestContext.init(
req_ctx_ = RequestContext.init(
req,
request_arena,
conn,
@@ -2759,6 +2756,7 @@ pub const Server = struct {
conn.client.deinit();
return;
};
var req_ctx = &req_ctx_;
req_ctx.timer.reset();
if (req_ctx.url.needs_redirect) {
@@ -2880,7 +2878,7 @@ pub const Server = struct {
if (comptime features.filesystem_router) {
if (!finished) {
req_ctx.bundler.router.?.match(server, RequestContext, &req_ctx) catch |err| {
req_ctx.bundler.router.?.match(server, RequestContext, req_ctx) catch |err| {
switch (err) {
error.ModuleNotFound => {
req_ctx.sendNotFound() catch {};

View File

@@ -544,3 +544,7 @@ pub const JSString = struct {
}
}
};
// not official api functions
pub extern "c" fn JSCInitialize() void;

View File

@@ -58,6 +58,8 @@
#include <wtf/text/StringView.h>
#include <wtf/text/WTFString.h>
#include <wtf/Gigacage.h>
#include <cstdlib>
#include <exception>
#include <iostream>
@@ -82,22 +84,21 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers;
bool has_loaded_jsc = false;
extern "C" JSC__JSGlobalObject *Zig__GlobalObject__create(JSClassRef *globalObjectClass, int count,
void *console_client) {
if (!has_loaded_jsc) {
extern "C" void JSCInitialize() {
if (has_loaded_jsc) return;
JSC::Options::useSourceProviderCache() = true;
JSC::Options::useUnlinkedCodeBlockJettisoning() = false;
// JSC::Options::useTopLevelAwait() = true;
JSC::Options::exposeInternalModuleLoader() = true;
std::set_terminate([]() { Zig__GlobalObject__onCrash(); });
// std::set_terminate([]() { Zig__GlobalObject__onCrash(); });
WTF::initializeMainThread();
JSC::initialize();
// Gigacage::disablePrimitiveGigacage();
has_loaded_jsc = true;
}
// JSC::Options::useCodeCache() = false;
}
extern "C" JSC__JSGlobalObject *Zig__GlobalObject__create(JSClassRef *globalObjectClass, int count,
void *console_client) {
auto heapSize = JSC::LargeHeap;
JSC::VM &vm = JSC::VM::create(heapSize).leakRef();

View File

@@ -22,6 +22,7 @@ pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) nore
}
pub var start_time: i128 = 0;
pub fn main() anyerror!void {
@import("javascript/jsc/JavascriptCore.zig").JSCInitialize();
start_time = std.time.nanoTimestamp();
// The memory allocator makes a massive difference.

View File

@@ -1 +1 @@
35057197d4ad54bc
4d09f9efba49d5ac

View File

@@ -60,17 +60,29 @@ pub const INotify = struct {
var eventlist: EventListBuffer = undefined;
var eventlist_ptrs: [128]*const INotifyEvent = undefined;
const add_mask = IN_EXCL_UNLINK | IN_MOVE_SELF | IN_CREATE | IN_DELETE | IN_DELETE_SELF;
var watch_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0);
pub fn watchPath(pathname: [*:0]const u8) !EventListIndex {
const watch_file_mask = IN_EXCL_UNLINK | IN_MOVE_SELF | IN_DELETE_SELF | IN_CLOSE_WRITE;
const watch_dir_mask = IN_EXCL_UNLINK | IN_DELETE | IN_DELETE_SELF | IN_CREATE | IN_MOVE_SELF | IN_ONLYDIR;
pub fn watchPath(pathname: [:0]const u8) !EventListIndex {
std.debug.assert(loaded_inotify);
return std.os.inotify_add_watchZ(inotify_fd, pathname, add_mask);
const old_count = watch_count.fetchAdd(1, .Release);
defer if (old_count == 0) std.Thread.Futex.wake(&watch_count, 10);
return std.os.inotify_add_watchZ(inotify_fd, pathname, watch_file_mask);
}
pub fn watchDir(pathname: [:0]const u8) !EventListIndex {
std.debug.assert(loaded_inotify);
const old_count = watch_count.fetchAdd(1, .Release);
defer if (old_count == 0) std.Thread.Futex.wake(&watch_count, 10);
return std.os.inotify_add_watchZ(inotify_fd, pathname, watch_dir_mask);
}
pub fn unwatch(wd: EventListIndex) void {
std.debug.assert(loaded_inotify);
_ = watch_count.fetchSub(1, .Release);
std.os.inotify_rm_watch(inotify_fd, wd);
}
@@ -84,6 +96,10 @@ pub const INotify = struct {
pub fn read() ![]*const INotifyEvent {
std.debug.assert(loaded_inotify);
restart: while (true) {
std.Thread.Futex.wait(&watch_count,0, null) catch unreachable;
const rc = std.os.system.read(
inotify_fd,
@ptrCast([*]u8, @alignCast(@alignOf([*]u8), &eventlist)),
@@ -110,12 +126,14 @@ pub const INotify = struct {
return eventlist_ptrs[0..count];
},
.AGAIN => continue :restart,
.INVAL => return error.ShortRead,
.BADF => return error.INotifyFailedToStart,
else => unreachable,
}
}
unreachable;
}
@@ -183,6 +201,21 @@ pub const WatchEvent = struct {
op: Op,
const KEvent = std.os.Kevent;
pub const Sorter = void;
pub fn sortByIndex(context: Sorter, event: WatchEvent, rhs: WatchEvent) bool {
return event.index < rhs.index;
}
pub fn merge(this: *WatchEvent, other: WatchEvent) void {
this.op = Op{
.delete = this.op.delete or other.op.delete,
.metadata = this.op.metadata or other.op.metadata,
.rename = this.op.rename or other.op.rename,
.write = this.op.write or other.op.write,
};
}
pub fn fromKEvent(this: *WatchEvent, kevent: KEvent) void {
this.* =
@@ -200,13 +233,10 @@ pub const WatchEvent = struct {
pub fn fromINotify(this: *WatchEvent, event: INotify.INotifyEvent, index: WatchItemIndex) void {
this.* = WatchEvent{
.op = Op{
.delete = (event.mask & INotify.IN_DELETE_SELF) > 0,
// only applies to directories
.metadata = (event.mask & INotify.IN_CREATE) > 0 or
(event.mask & INotify.IN_DELETE) > 0 or
(event.mask & INotify.IN_MOVE) > 0,
.delete = (event.mask & INotify.IN_DELETE_SELF) > 0 or (event.mask & INotify.IN_DELETE) > 0,
.metadata = false,
.rename = (event.mask & INotify.IN_MOVE_SELF) > 0,
.write = (event.mask & INotify.IN_MODIFY) > 0,
.write = (event.mask & INotify.IN_MODIFY) > 0 or (event.mask & INotify.IN_MOVE) > 0,
},
.index = index,
};
@@ -257,6 +287,8 @@ pub fn NewWatcher(comptime ContextType: type) type {
pub fn init(ctx: ContextType, fs: *Fs.FileSystem, allocator: *std.mem.Allocator) !*Watcher {
var watcher = try allocator.create(Watcher);
try PlatformWatcher.init();
watcher.* = Watcher{
.fs = fs,
.fd = 0,
@@ -272,7 +304,6 @@ pub fn NewWatcher(comptime ContextType: type) type {
}
pub fn start(this: *Watcher) !void {
try PlatformWatcher.init();
std.debug.assert(this.watchloop_handle == null);
var thread = try std.Thread.spawn(.{}, Watcher.watchLoop, .{this});
thread.setName("File Watcher") catch {};
@@ -389,7 +420,7 @@ pub fn NewWatcher(comptime ContextType: type) type {
this.ctx.onFileUpdate(watchevents, this.watchlist);
}
} else if (Environment.isLinux) {
while (true) {
restart: while (true) {
defer Output.flush();
var events = try INotify.read();
@@ -417,7 +448,21 @@ pub fn NewWatcher(comptime ContextType: type) type {
watch_event_id += 1;
}
this.ctx.onFileUpdate(watchevents[0..watch_event_id], this.watchlist);
var all_events = watchevents[0..watch_event_id];
std.sort.sort(WatchEvent, all_events, void{}, WatchEvent.sortByIndex);
var last_event_index: usize = 0;
var last_event_id: INotify.EventListIndex = std.math.maxInt(INotify.EventListIndex);
for (all_events) |event, i| {
if (event.index == last_event_id) {
all_events[last_event_index].merge(event);
continue;
}
last_event_index = i;
last_event_id = event.index;
}
if (all_events.len == 0) continue :restart;
this.ctx.onFileUpdate(all_events[0..last_event_index+1], this.watchlist);
remaining_events -= slice.len;
}
}
@@ -503,11 +548,13 @@ pub fn NewWatcher(comptime ContextType: type) type {
null,
);
} else if (Environment.isLinux) {
var sentineled = file_path_;
var file_path_to_use_ptr: [*c]u8 = @intToPtr([*c]u8, @ptrToInt(file_path_.ptr));
var file_path_to_use: [:0]u8 = file_path_to_use_ptr[0..sentineled.len :0];
index = try INotify.watchPath(file_path_to_use);
// var file_path_to_use_ = std.mem.trimRight(u8, file_path_, "/");
// var buf: [std.fs.MAX_PATH_BYTES+1]u8 = undefined;
// std.mem.copy(u8, &buf, file_path_to_use_);
// buf[file_path_to_use_.len] = 0;
var buf = file_path_.ptr;
var slice: [:0]const u8 = buf[0..file_path_.len:0];
index = try INotify.watchPath(slice);
}
this.watchlist.appendAssumeCapacity(.{
@@ -587,12 +634,12 @@ pub fn NewWatcher(comptime ContextType: type) type {
null,
);
} else if (Environment.isLinux) {
// This works around a Zig compiler bug when casting a slice from a string to a sentineled string.
var sentineled = file_path_;
var file_path_to_use_ptr: [*c]u8 = @intToPtr([*c]u8, @ptrToInt(file_path_.ptr));
var file_path_to_use: [:0]u8 = file_path_to_use_ptr[0..sentineled.len :0];
index = try INotify.watchPath(file_path_to_use);
var file_path_to_use_ = std.mem.trimRight(u8, file_path_, "/");
var buf: [std.fs.MAX_PATH_BYTES+1]u8 = undefined;
std.mem.copy(u8, &buf, file_path_to_use_);
buf[file_path_to_use_.len] = 0;
var slice: [:0]u8 = buf[0..file_path_to_use_.len:0];
index = try INotify.watchDir(slice);
}
this.watchlist.appendAssumeCapacity(.{