Commit 821805aa92

Andrew Kelley <superjoe30@gmail.com>
2018-08-02 23:04:17
WIP: Channel.getOrNull
1 parent e3ae2cf
src-self-hosted/compilation.zig
@@ -717,13 +717,13 @@ pub const Compilation = struct {
     }
 
     async fn buildAsync(self: *Compilation) void {
-        while (true) {
-            // TODO directly awaiting async should guarantee memory allocation elision
-            const build_result = await (async self.compileAndLink() catch unreachable);
+        var build_result = await (async self.initialCompile() catch unreachable);
 
+        while (true) {
+            const link_result = if (build_result) self.maybeLink() else |err| err;
             // this makes a handy error return trace and stack trace in debug mode
             if (std.debug.runtime_safety) {
-                build_result catch unreachable;
+                link_result catch unreachable;
             }
 
             const compile_errors = blk: {
@@ -732,7 +732,7 @@ pub const Compilation = struct {
                 break :blk held.value.toOwnedSlice();
             };
 
-            if (build_result) |_| {
+            if (link_result) |_| {
                 if (compile_errors.len == 0) {
                     await (async self.events.put(Event.Ok) catch unreachable);
                 } else {
@@ -745,108 +745,158 @@ pub const Compilation = struct {
                 await (async self.events.put(Event{ .Error = err }) catch unreachable);
             }
 
-            // for now we stop after 1
-            return;
+            var group = event.Group(BuildError!void).init(self.loop);
+            while (self.fs_watch.channel.getOrNull()) |root_scope| {
+                try group.call(rebuildFile, self, root_scope);
+            }
+            build_result = await (async group.wait() catch unreachable);
         }
     }
 
-    async fn compileAndLink(self: *Compilation) !void {
-        if (self.root_src_path) |root_src_path| {
-            // TODO async/await os.path.real
-            const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| {
-                try printError("unable to get real path '{}': {}", root_src_path, err);
+    async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) !void {
+        const tree_scope = blk: {
+            const source_code = (await (async fs.readFile(
+                self.loop,
+                root_src_real_path,
+                max_src_size,
+            ) catch unreachable)) catch |err| {
+                try printError("unable to open '{}': {}", root_src_real_path, err);
                 return err;
             };
-            const root_scope = blk: {
-                errdefer self.gpa().free(root_src_real_path);
+            errdefer self.gpa().free(source_code);
 
-                const source_code = (await (async fs.readFile(
-                    self.loop,
-                    root_src_real_path,
-                    max_src_size,
-                ) catch unreachable)) catch |err| {
-                    try printError("unable to open '{}': {}", root_src_real_path, err);
-                    return err;
-                };
-                errdefer self.gpa().free(source_code);
+            const tree = try self.gpa().createOne(ast.Tree);
+            tree.* = try std.zig.parse(self.gpa(), source_code);
+            errdefer {
+                tree.deinit();
+                self.gpa().destroy(tree);
+            }
 
-                const tree = try self.gpa().createOne(ast.Tree);
-                tree.* = try std.zig.parse(self.gpa(), source_code);
-                errdefer {
-                    tree.deinit();
-                    self.gpa().destroy(tree);
-                }
+            break :blk try Scope.AstTree.create(self, tree, root_scope);
+        };
+        defer tree_scope.base.deref(self);
 
-                break :blk try Scope.Root.create(self, tree, root_src_real_path);
-            };
-            defer root_scope.base.deref(self);
-            const tree = root_scope.tree;
+        var error_it = tree_scope.tree.errors.iterator(0);
+        while (error_it.next()) |parse_error| {
+            const msg = try Msg.createFromParseErrorAndScope(self, tree_scope, parse_error);
+            errdefer msg.destroy();
 
-            var error_it = tree.errors.iterator(0);
-            while (error_it.next()) |parse_error| {
-                const msg = try Msg.createFromParseErrorAndScope(self, root_scope, parse_error);
-                errdefer msg.destroy();
+            try await (async self.addCompileErrorAsync(msg) catch unreachable);
+        }
+        if (tree_scope.tree.errors.len != 0) {
+            return;
+        }
 
-                try await (async self.addCompileErrorAsync(msg) catch unreachable);
-            }
-            if (tree.errors.len != 0) {
-                return;
-            }
+        const locked_table = await (async root_scope.decls.table.acquireWrite() catch unreachable);
+        defer locked_table.release();
 
-            const decls = try Scope.Decls.create(self, &root_scope.base);
-            defer decls.base.deref(self);
+        var decl_group = event.Group(BuildError!void).init(self.loop);
+        defer decl_group.deinit();
 
-            var decl_group = event.Group(BuildError!void).init(self.loop);
-            var decl_group_consumed = false;
-            errdefer if (!decl_group_consumed) decl_group.cancelAll();
+        try self.rebuildChangedDecls(
+            &decl_group,
+            locked_table,
+            root_scope.decls,
+            &tree_scope.tree.root_node.decls,
+            tree_scope,
+        );
 
-            var it = tree.root_node.decls.iterator(0);
-            while (it.next()) |decl_ptr| {
-                const decl = decl_ptr.*;
-                switch (decl.id) {
-                    ast.Node.Id.Comptime => {
-                        const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl);
+        try await (async decl_group.wait() catch unreachable);
+    }
 
-                        try self.prelink_group.call(addCompTimeBlock, self, &decls.base, comptime_node);
-                    },
-                    ast.Node.Id.VarDecl => @panic("TODO"),
-                    ast.Node.Id.FnProto => {
-                        const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
-
-                        const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else {
-                            try self.addCompileError(root_scope, Span{
-                                .first = fn_proto.fn_token,
-                                .last = fn_proto.fn_token + 1,
-                            }, "missing function name");
-                            continue;
-                        };
+    async fn rebuildChangedDecls(
+        self: *Compilation,
+        group: *event.Group(BuildError!void),
+        locked_table: *Decl.Table,
+        decl_scope: *Scope.Decls,
+        ast_decls: &ast.Node.Root.DeclList,
+        tree_scope: *Scope.AstTree,
+    ) !void {
+        var existing_decls = try locked_table.clone();
+        defer existing_decls.deinit();
+
+        var ast_it = ast_decls.iterator(0);
+        while (ast_it.next()) |decl_ptr| {
+            const decl = decl_ptr.*;
+            switch (decl.id) {
+                ast.Node.Id.Comptime => {
+                    const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl);
+
+                    // TODO connect existing comptime decls to updated source files
 
+                    try self.prelink_group.call(addCompTimeBlock, self, &decl_scope.base, comptime_node);
+                },
+                ast.Node.Id.VarDecl => @panic("TODO"),
+                ast.Node.Id.FnProto => {
+                    const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
+
+                    const name = if (fn_proto.name_token) |name_token| tree_scope.tree.tokenSlice(name_token) else {
+                        try self.addCompileError(root_scope, Span{
+                            .first = fn_proto.fn_token,
+                            .last = fn_proto.fn_token + 1,
+                        }, "missing function name");
+                        continue;
+                    };
+
+                    if (existing_decls.remove(name)) |entry| {
+                        // compare new code to existing
+                        const existing_decl = entry.value;
+                        // Just compare the old bytes to the new bytes of the top level decl.
+                        // Even if the AST is technically the same, we want error messages to display
+                        // from the most recent source.
+                        @panic("TODO handle decl comparison");
+                        // Add the new thing before dereferencing the old thing. This way we don't end
+                        // up pointlessly re-creating things we end up using in the new thing.
+                    } else {
+                        // add new decl
                         const fn_decl = try self.gpa().create(Decl.Fn{
                             .base = Decl{
                                 .id = Decl.Id.Fn,
                                 .name = name,
-                                .visib = parseVisibToken(tree, fn_proto.visib_token),
+                                .visib = parseVisibToken(tree_scope.tree, fn_proto.visib_token),
                                 .resolution = event.Future(BuildError!void).init(self.loop),
-                                .parent_scope = &decls.base,
+                                .parent_scope = &decl_scope.base,
                             },
                             .value = Decl.Fn.Val{ .Unresolved = {} },
                             .fn_proto = fn_proto,
                         });
                         errdefer self.gpa().destroy(fn_decl);
 
-                        try decl_group.call(addTopLevelDecl, self, decls, &fn_decl.base);
-                    },
-                    ast.Node.Id.TestDecl => @panic("TODO"),
-                    else => unreachable,
-                }
+                        try group.call(addTopLevelDecl, self, &fn_decl.base, locked_table);
+                    }
+                },
+                ast.Node.Id.TestDecl => @panic("TODO"),
+                else => unreachable,
             }
-            decl_group_consumed = true;
-            try await (async decl_group.wait() catch unreachable);
+        }
+
+        var existing_decl_it = existing_decls.iterator();
+        while (existing_decl_it.next()) |entry| {
+            // this decl was deleted
+            const existing_decl = entry.value;
+            @panic("TODO handle decl deletion");
+        }
+    }
+
+    async fn initialCompile(self: *Compilation) !void {
+        if (self.root_src_path) |root_src_path| {
+            const root_scope = blk: {
+                // TODO async/await os.path.real
+                const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| {
+                    try printError("unable to get real path '{}': {}", root_src_path, err);
+                    return err;
+                };
+                errdefer self.gpa().free(root_src_real_path);
 
-            // Now other code can rely on the decls scope having a complete list of names.
-            decls.name_future.resolve();
+                break :blk try Scope.Root.create(self, root_src_real_path);
+            };
+            defer root_scope.base.deref(self);
+
+            try self.rebuildFile(root_scope);
         }
+    }
 
+    async fn maybeLink(self: *Compilation) !void {
         (await (async self.prelink_group.wait() catch unreachable)) catch |err| switch (err) {
             error.SemanticAnalysisFailed => {},
             else => return err,
@@ -920,28 +970,20 @@ pub const Compilation = struct {
         analyzed_code.destroy(comp.gpa());
     }
 
-    async fn addTopLevelDecl(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void {
+    async fn addTopLevelDecl(
+        self: *Compilation,
+        decl: *Decl,
+        locked_table: *Decl.Table,
+    ) !void {
         const tree = decl.findRootScope().tree;
         const is_export = decl.isExported(tree);
 
-        var add_to_table_resolved = false;
-        const add_to_table = async self.addDeclToTable(decls, decl) catch unreachable;
-        errdefer if (!add_to_table_resolved) cancel add_to_table; // TODO https://github.com/ziglang/zig/issues/1261
-
         if (is_export) {
             try self.prelink_group.call(verifyUniqueSymbol, self, decl);
             try self.prelink_group.call(resolveDecl, self, decl);
         }
 
-        add_to_table_resolved = true;
-        try await add_to_table;
-    }
-
-    async fn addDeclToTable(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void {
-        const held = await (async decls.table.acquire() catch unreachable);
-        defer held.release();
-
-        if (try held.value.put(decl.name, decl)) |other_decl| {
+        if (try locked_table.put(decl.name, decl)) |other_decl| {
             try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name);
             // TODO note: other definition here
         }
src-self-hosted/decl.zig
@@ -16,6 +16,7 @@ pub const Decl = struct {
     visib: Visib,
     resolution: event.Future(Compilation.BuildError!void),
     parent_scope: *Scope,
+    tree_scope: *Scope.AstTree,
 
     pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8);
 
src-self-hosted/errmsg.zig
@@ -49,7 +49,7 @@ pub const Msg = struct {
     };
 
     const ScopeAndComp = struct {
-        root_scope: *Scope.Root,
+        tree_scope: *Scope.AstTree,
         compilation: *Compilation,
     };
 
@@ -60,7 +60,7 @@ pub const Msg = struct {
                 path_and_tree.allocator.destroy(self);
             },
             Data.ScopeAndComp => |scope_and_comp| {
-                scope_and_comp.root_scope.base.deref(scope_and_comp.compilation);
+                scope_and_comp.tree_scope.base.deref(scope_and_comp.compilation);
                 scope_and_comp.compilation.gpa().free(self.text);
                 scope_and_comp.compilation.gpa().destroy(self);
             },
@@ -84,7 +84,7 @@ pub const Msg = struct {
                 return path_and_tree.realpath;
             },
             Data.ScopeAndComp => |scope_and_comp| {
-                return scope_and_comp.root_scope.realpath;
+                return scope_and_comp.tree_scope.root().realpath;
             },
         }
     }
@@ -95,31 +95,31 @@ pub const Msg = struct {
                 return path_and_tree.tree;
             },
             Data.ScopeAndComp => |scope_and_comp| {
-                return scope_and_comp.root_scope.tree;
+                return scope_and_comp.tree_scope.tree;
             },
         }
     }
 
     /// Takes ownership of text
-    /// References root_scope, and derefs when the msg is freed
-    pub fn createFromScope(comp: *Compilation, root_scope: *Scope.Root, span: Span, text: []u8) !*Msg {
+    /// References tree_scope, and derefs when the msg is freed
+    pub fn createFromScope(comp: *Compilation, tree_scope: *Scope.AstTree, span: Span, text: []u8) !*Msg {
         const msg = try comp.gpa().create(Msg{
             .text = text,
             .span = span,
             .data = Data{
                 .ScopeAndComp = ScopeAndComp{
-                    .root_scope = root_scope,
+                    .tree_scope = tree_scope,
                     .compilation = comp,
                 },
             },
         });
-        root_scope.base.ref();
+        tree_scope.base.ref();
         return msg;
     }
 
     pub fn createFromParseErrorAndScope(
         comp: *Compilation,
-        root_scope: *Scope.Root,
+        tree_scope: *Scope.AstTree,
         parse_error: *const ast.Error,
     ) !*Msg {
         const loc_token = parse_error.loc();
@@ -127,7 +127,7 @@ pub const Msg = struct {
         defer text_buf.deinit();
 
         var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
-        try parse_error.render(&root_scope.tree.tokens, out_stream);
+        try parse_error.render(&tree_scope.tree.tokens, out_stream);
 
         const msg = try comp.gpa().create(Msg{
             .text = undefined,
@@ -137,12 +137,12 @@ pub const Msg = struct {
             },
             .data = Data{
                 .ScopeAndComp = ScopeAndComp{
-                    .root_scope = root_scope,
+                    .tree_scope = tree_scope,
                     .compilation = comp,
                 },
             },
         });
-        root_scope.base.ref();
+        tree_scope.base.ref();
         msg.text = text_buf.toOwnedSlice();
         return msg;
     }
src-self-hosted/ir.zig
@@ -1929,8 +1929,9 @@ pub const Builder = struct {
                 Scope.Id.Root => return Ident.NotFound,
                 Scope.Id.Decls => {
                     const decls = @fieldParentPtr(Scope.Decls, "base", s);
-                    const table = await (async decls.getTableReadOnly() catch unreachable);
-                    if (table.get(name)) |entry| {
+                    const locked_table = await (async decls.table.acquireRead() catch unreachable);
+                    defer locked_table.release();
+                    if (locked_table.value.get(name)) |entry| {
                         return Ident{ .Decl = entry.value };
                     }
                 },
src-self-hosted/libc_installation.zig
@@ -143,7 +143,7 @@ pub const LibCInstallation = struct {
     pub async fn findNative(self: *LibCInstallation, loop: *event.Loop) !void {
         self.initEmpty();
         var group = event.Group(FindError!void).init(loop);
-        errdefer group.cancelAll();
+        errdefer group.deinit();
         var windows_sdk: ?*c.ZigWindowsSDK = null;
         errdefer if (windows_sdk) |sdk| c.zig_free_windows_sdk(@ptrCast(?[*]c.ZigWindowsSDK, sdk));
 
@@ -313,7 +313,7 @@ pub const LibCInstallation = struct {
             },
         };
         var group = event.Group(FindError!void).init(loop);
-        errdefer group.cancelAll();
+        errdefer group.deinit();
         for (dyn_tests) |*dyn_test| {
             try group.call(testNativeDynamicLinker, self, loop, dyn_test);
         }
@@ -341,7 +341,6 @@ pub const LibCInstallation = struct {
         }
     }
 
-
     async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void {
         var search_buf: [2]Search = undefined;
         const searches = fillSearch(&search_buf, sdk);
@@ -450,7 +449,6 @@ fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
     return search_buf[0..search_end];
 }
 
-
 fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool {
     if (std.os.File.access(allocator, path)) |_| {
         return true;
src-self-hosted/main.zig
@@ -71,26 +71,26 @@ pub fn main() !void {
     }
 
     const commands = []Command{
-        Command{
-            .name = "build-exe",
-            .exec = cmdBuildExe,
-        },
-        Command{
-            .name = "build-lib",
-            .exec = cmdBuildLib,
-        },
-        Command{
-            .name = "build-obj",
-            .exec = cmdBuildObj,
-        },
+        //Command{
+        //    .name = "build-exe",
+        //    .exec = cmdBuildExe,
+        //},
+        //Command{
+        //    .name = "build-lib",
+        //    .exec = cmdBuildLib,
+        //},
+        //Command{
+        //    .name = "build-obj",
+        //    .exec = cmdBuildObj,
+        //},
         Command{
             .name = "fmt",
             .exec = cmdFmt,
         },
-        Command{
-            .name = "libc",
-            .exec = cmdLibC,
-        },
+        //Command{
+        //    .name = "libc",
+        //    .exec = cmdLibC,
+        //},
         Command{
             .name = "targets",
             .exec = cmdTargets,
@@ -472,23 +472,22 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
 }
 
 async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void {
-    // TODO directly awaiting async should guarantee memory allocation elision
-    const build_event = await (async comp.events.get() catch unreachable);
-
-    switch (build_event) {
-        Compilation.Event.Ok => {
-            return;
-        },
-        Compilation.Event.Error => |err| {
-            std.debug.warn("build failed: {}\n", @errorName(err));
-            os.exit(1);
-        },
-        Compilation.Event.Fail => |msgs| {
-            for (msgs) |msg| {
-                defer msg.destroy();
-                msg.printToFile(&stderr_file, color) catch os.exit(1);
-            }
-        },
+    while (true) {
+        // TODO directly awaiting async should guarantee memory allocation elision
+        const build_event = await (async comp.events.get() catch unreachable);
+
+        switch (build_event) {
+            Compilation.Event.Ok => {},
+            Compilation.Event.Error => |err| {
+                stderr.print("build failed: {}\n", @errorName(err)) catch os.exit(1);
+            },
+            Compilation.Event.Fail => |msgs| {
+                for (msgs) |msg| {
+                    defer msg.destroy();
+                    msg.printToFile(&stderr_file, color) catch os.exit(1);
+                }
+            },
+        }
     }
 }
 
src-self-hosted/scope.zig
@@ -36,6 +36,7 @@ pub const Scope = struct {
                 Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp),
                 Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp),
                 Id.Var => @fieldParentPtr(Var, "base", base).destroy(comp),
+                Id.AstTree => @fieldParentPtr(AstTree, "base", base).destroy(comp),
             }
         }
     }
@@ -97,6 +98,7 @@ pub const Scope = struct {
 
     pub const Id = enum {
         Root,
+        AstTree,
         Decls,
         Block,
         FnDef,
@@ -108,13 +110,12 @@ pub const Scope = struct {
 
     pub const Root = struct {
         base: Scope,
-        tree: *ast.Tree,
         realpath: []const u8,
+        decls: *Decls,
 
         /// Creates a Root scope with 1 reference
         /// Takes ownership of realpath
-        /// Takes ownership of tree, will deinit and destroy when done.
-        pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root {
+        pub fn create(comp: *Compilation, realpath: []u8) !*Root {
             const self = try comp.gpa().createOne(Root);
             self.* = Root{
                 .base = Scope{
@@ -122,40 +123,64 @@ pub const Scope = struct {
                     .parent = null,
                     .ref_count = std.atomic.Int(usize).init(1),
                 },
-                .tree = tree,
                 .realpath = realpath,
+                .decls = undefined,
             };
-
+            errdefer comp.gpa().destroy(self);
+            self.decls = try Decls.create(comp, &self.base);
             return self;
         }
 
         pub fn destroy(self: *Root, comp: *Compilation) void {
+            self.decls.base.deref(comp);
+            comp.gpa().free(self.realpath);
+            comp.gpa().destroy(self);
+        }
+    };
+
+    pub const AstTree = struct {
+        base: Scope,
+        tree: *ast.Tree,
+
+        /// Creates a scope with 1 reference
+        /// Takes ownership of tree, will deinit and destroy when done.
+        pub fn create(comp: *Compilation, tree: *ast.Tree, root: *Root) !*AstTree {
+            const self = try comp.gpa().createOne(Root);
+            self.* = AstTree{
+                .base = undefined,
+                .tree = tree,
+            };
+            self.base.init(Id.AstTree, &root.base);
+
+            return self;
+        }
+
+        pub fn destroy(self: *AstTree, comp: *Compilation) void {
             comp.gpa().free(self.tree.source);
             self.tree.deinit();
             comp.gpa().destroy(self.tree);
-            comp.gpa().free(self.realpath);
             comp.gpa().destroy(self);
         }
+
+        pub fn root(self: *AstTree) *Root {
+            return self.base.findRoot();
+        }
     };
 
     pub const Decls = struct {
         base: Scope,
 
-        /// The lock must be respected for writing. However once name_future resolves,
-        /// readers can freely access it.
-        table: event.Locked(Decl.Table),
-
-        /// Once this future is resolved, the table is complete and available for unlocked
-        /// read-only access. It does not mean all the decls are resolved; it means only that
-        /// the table has all the names. Each decl in the table has its own resolution state.
-        name_future: event.Future(void),
+        /// This table remains Write Locked when the names are incomplete or possibly outdated.
+        /// So if a reader manages to grab a lock, it can be sure that the set of names is complete
+        /// and correct.
+        table: event.RwLocked(Decl.Table),
 
         /// Creates a Decls scope with 1 reference
         pub fn create(comp: *Compilation, parent: *Scope) !*Decls {
             const self = try comp.gpa().createOne(Decls);
             self.* = Decls{
                 .base = undefined,
-                .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())),
+                .table = event.RwLocked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())),
                 .name_future = event.Future(void).init(comp.loop),
             };
             self.base.init(Id.Decls, parent);
@@ -166,11 +191,6 @@ pub const Scope = struct {
             self.table.deinit();
             comp.gpa().destroy(self);
         }
-
-        pub async fn getTableReadOnly(self: *Decls) *Decl.Table {
-            _ = await (async self.name_future.get() catch unreachable);
-            return &self.table.private_data;
-        }
     };
 
     pub const Block = struct {
src-self-hosted/test.zig
@@ -50,7 +50,7 @@ pub const TestContext = struct {
         errdefer self.event_loop_local.deinit();
 
         self.group = std.event.Group(error!void).init(&self.loop);
-        errdefer self.group.cancelAll();
+        errdefer self.group.deinit();
 
         self.zig_lib_dir = try introspect.resolveZigLibDir(allocator);
         errdefer allocator.free(self.zig_lib_dir);
std/atomic/queue.zig
@@ -1,40 +1,38 @@
+const std = @import("../index.zig");
 const builtin = @import("builtin");
 const AtomicOrder = builtin.AtomicOrder;
 const AtomicRmwOp = builtin.AtomicRmwOp;
+const assert = std.debug.assert;
 
 /// Many producer, many consumer, non-allocating, thread-safe.
-/// Uses a spinlock to protect get() and put().
+/// Uses a mutex to protect access.
 pub fn Queue(comptime T: type) type {
     return struct {
         head: ?*Node,
         tail: ?*Node,
-        lock: u8,
+        mutex: std.Mutex,
 
         pub const Self = this;
-
-        pub const Node = struct {
-            next: ?*Node,
-            data: T,
-        };
+        pub const Node = std.LinkedList(T).Node;
 
         pub fn init() Self {
             return Self{
                 .head = null,
                 .tail = null,
-                .lock = 0,
+                .mutex = std.Mutex.init(),
             };
         }
 
         pub fn put(self: *Self, node: *Node) void {
             node.next = null;
 
-            while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
-            defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+            const held = self.mutex.acquire();
+            defer held.release();
 
-            const opt_tail = self.tail;
+            node.prev = self.tail;
             self.tail = node;
-            if (opt_tail) |tail| {
-                tail.next = node;
+            if (node.prev) |prev_tail| {
+                prev_tail.next = node;
             } else {
                 assert(self.head == null);
                 self.head = node;
@@ -42,18 +40,27 @@ pub fn Queue(comptime T: type) type {
         }
 
         pub fn get(self: *Self) ?*Node {
-            while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
-            defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+            const held = self.mutex.acquire();
+            defer held.release();
 
             const head = self.head orelse return null;
             self.head = head.next;
-            if (head.next == null) self.tail = null;
+            if (head.next) |new_head| {
+                new_head.prev = null;
+            } else {
+                self.tail = null;
+            }
+            // This way, a get() and a remove() are thread-safe with each other.
+            head.prev = null;
+            head.next = null;
             return head;
         }
 
         pub fn unget(self: *Self, node: *Node) void {
-            while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
-            defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+            node.prev = null;
+
+            const held = self.mutex.acquire();
+            defer held.release();
 
             const opt_head = self.head;
             self.head = node;
@@ -65,13 +72,39 @@ pub fn Queue(comptime T: type) type {
             }
         }
 
+        /// Thread-safe with get() and remove(). Returns whether node was actually removed.
+        pub fn remove(self: *Self, node: *Node) bool {
+            const held = self.mutex.acquire();
+            defer held.release();
+
+            if (node.prev == null and node.next == null and self.head != node) {
+                return false;
+            }
+
+            if (node.prev) |prev| {
+                prev.next = node.next;
+            } else {
+                self.head = node.next;
+            }
+            if (node.next) |next| {
+                next.prev = node.prev;
+            } else {
+                self.tail = node.prev;
+            }
+            node.prev = null;
+            node.next = null;
+            return true;
+        }
+
         pub fn isEmpty(self: *Self) bool {
-            return @atomicLoad(?*Node, &self.head, builtin.AtomicOrder.SeqCst) != null;
+            const held = self.mutex.acquire();
+            defer held.release();
+            return self.head != null;
         }
 
         pub fn dump(self: *Self) void {
-            while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
-            defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+            const held = self.mutex.acquire();
+            defer held.release();
 
             std.debug.warn("head: ");
             dumpRecursive(self.head, 0);
@@ -93,9 +126,6 @@ pub fn Queue(comptime T: type) type {
     };
 }
 
-const std = @import("../index.zig");
-const assert = std.debug.assert;
-
 const Context = struct {
     allocator: *std.mem.Allocator,
     queue: *Queue(i32),
@@ -169,6 +199,7 @@ fn startPuts(ctx: *Context) u8 {
         std.os.time.sleep(0, 1); // let the os scheduler be our fuzz
         const x = @bitCast(i32, r.random.scalar(u32));
         const node = ctx.allocator.create(Queue(i32).Node{
+            .prev = undefined,
             .next = undefined,
             .data = x,
         }) catch unreachable;
@@ -198,12 +229,14 @@ test "std.atomic.Queue single-threaded" {
     var node_0 = Queue(i32).Node{
         .data = 0,
         .next = undefined,
+        .prev = undefined,
     };
     queue.put(&node_0);
 
     var node_1 = Queue(i32).Node{
         .data = 1,
         .next = undefined,
+        .prev = undefined,
     };
     queue.put(&node_1);
 
@@ -212,12 +245,14 @@ test "std.atomic.Queue single-threaded" {
     var node_2 = Queue(i32).Node{
         .data = 2,
         .next = undefined,
+        .prev = undefined,
     };
     queue.put(&node_2);
 
     var node_3 = Queue(i32).Node{
         .data = 3,
         .next = undefined,
+        .prev = undefined,
     };
     queue.put(&node_3);
 
@@ -228,6 +263,7 @@ test "std.atomic.Queue single-threaded" {
     var node_4 = Queue(i32).Node{
         .data = 4,
         .next = undefined,
+        .prev = undefined,
     };
     queue.put(&node_4);
 
std/event/channel.zig
@@ -5,7 +5,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
 const AtomicOrder = builtin.AtomicOrder;
 const Loop = std.event.Loop;
 
-/// many producer, many consumer, thread-safe, lock-free, runtime configurable buffer size
+/// many producer, many consumer, thread-safe, runtime configurable buffer size
 /// when buffer is empty, consumers suspend and are resumed by producers
 /// when buffer is full, producers suspend and are resumed by consumers
 pub fn Channel(comptime T: type) type {
@@ -13,6 +13,7 @@ pub fn Channel(comptime T: type) type {
         loop: *Loop,
 
         getters: std.atomic.Queue(GetNode),
+        or_null_queue: std.atomic.Queue(*std.atomic.Queue(GetNode).Node),
         putters: std.atomic.Queue(PutNode),
         get_count: usize,
         put_count: usize,
@@ -26,8 +27,22 @@ pub fn Channel(comptime T: type) type {
 
         const SelfChannel = this;
         const GetNode = struct {
-            ptr: *T,
             tick_node: *Loop.NextTickNode,
+            data: Data,
+
+            const Data = union(enum) {
+                Normal: Normal,
+                OrNull: OrNull,
+            };
+
+            const Normal = struct {
+                ptr: *T,
+            };
+
+            const OrNull = struct {
+                ptr: *?T,
+                or_null: *std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node,
+            };
         };
         const PutNode = struct {
             data: T,
@@ -48,6 +63,7 @@ pub fn Channel(comptime T: type) type {
                 .need_dispatch = 0,
                 .getters = std.atomic.Queue(GetNode).init(),
                 .putters = std.atomic.Queue(PutNode).init(),
+                .or_null_queue = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).init(),
                 .get_count = 0,
                 .put_count = 0,
             });
@@ -71,18 +87,31 @@ pub fn Channel(comptime T: type) type {
         /// puts a data item in the channel. The promise completes when the value has been added to the
         /// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter.
         pub async fn put(self: *SelfChannel, data: T) void {
+            // TODO fix this workaround
+            var my_handle: promise = undefined;
+            suspend |p| {
+                my_handle = p;
+                resume p;
+            }
+
+            var my_tick_node = Loop.NextTickNode.init(my_handle);
+            var queue_node = std.atomic.Queue(PutNode).Node.init(PutNode{
+                .tick_node = &my_tick_node,
+                .data = data,
+            });
+
+            // TODO test canceling a put()
+            errdefer {
+                _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+                const need_dispatch = !self.putters.remove(&queue_node);
+                self.loop.cancelOnNextTick(&my_tick_node);
+                if (need_dispatch) {
+                    // oops we made the put_count incorrect for a period of time. fix by dispatching.
+                    _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+                    self.dispatch();
+                }
+            }
             suspend |handle| {
-                var my_tick_node = Loop.NextTickNode{
-                    .next = undefined,
-                    .data = handle,
-                };
-                var queue_node = std.atomic.Queue(PutNode).Node{
-                    .data = PutNode{
-                        .tick_node = &my_tick_node,
-                        .data = data,
-                    },
-                    .next = undefined,
-                };
                 self.putters.put(&queue_node);
                 _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
 
@@ -93,21 +122,37 @@ pub fn Channel(comptime T: type) type {
         /// await this function to get an item from the channel. If the buffer is empty, the promise will
         /// complete when the next item is put in the channel.
         pub async fn get(self: *SelfChannel) T {
+            // TODO fix this workaround
+            var my_handle: promise = undefined;
+            suspend |p| {
+                my_handle = p;
+                resume p;
+            }
+
             // TODO integrate this function with named return values
             // so we can get rid of this extra result copy
             var result: T = undefined;
-            suspend |handle| {
-                var my_tick_node = Loop.NextTickNode{
-                    .next = undefined,
-                    .data = handle,
-                };
-                var queue_node = std.atomic.Queue(GetNode).Node{
-                    .data = GetNode{
-                        .ptr = &result,
-                        .tick_node = &my_tick_node,
-                    },
-                    .next = undefined,
-                };
+            var my_tick_node = Loop.NextTickNode.init(my_handle);
+            var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
+                .tick_node = &my_tick_node,
+                .data = GetNode.Data{
+                    .Normal = GetNode.Normal{ .ptr = &result },
+                },
+            });
+
+            // TODO test canceling a get()
+            errdefer {
+                _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+                const need_dispatch = !self.getters.remove(&queue_node);
+                self.loop.cancelOnNextTick(&my_tick_node);
+                if (need_dispatch) {
+                    // oops we made the get_count incorrect for a period of time. fix by dispatching.
+                    _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+                    self.dispatch();
+                }
+            }
+
+            suspend |_| {
                 self.getters.put(&queue_node);
                 _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
 
@@ -116,8 +161,64 @@ pub fn Channel(comptime T: type) type {
             return result;
         }
 
-        fn getOrNull(self: *SelfChannel) ?T {
-            TODO();
+        //pub async fn select(comptime EnumUnion: type, channels: ...) EnumUnion {
+        //    assert(@memberCount(EnumUnion) == channels.len); // enum union and channels mismatch
+        //    assert(channels.len != 0); // enum unions cannot have 0 fields
+        //    if (channels.len == 1) {
+        //        const result = await (async channels[0].get() catch unreachable);
+        //        return @unionInit(EnumUnion, @memberName(EnumUnion, 0), result);
+        //    }
+        //}
+
+        /// Await this function to get an item from the channel. If the buffer is empty and there are no
+        /// puts waiting, this returns null.
+        /// Await is necessary for locking purposes. The function will be resumed after checking the channel
+        /// for data and will not wait for data to be available.
+        pub async fn getOrNull(self: *SelfChannel) ?T {
+            // TODO fix this workaround
+            var my_handle: promise = undefined;
+            suspend |p| {
+                my_handle = p;
+                resume p;
+            }
+
+            // TODO integrate this function with named return values
+            // so we can get rid of this extra result copy
+            var result: ?T = null;
+            var my_tick_node = Loop.NextTickNode.init(my_handle);
+            var or_null_node = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node.init(undefined);
+            var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
+                .tick_node = &my_tick_node,
+                .data = GetNode.Data{
+                    .OrNull = GetNode.OrNull{
+                        .ptr = &result,
+                        .or_null = &or_null_node,
+                    },
+                },
+            });
+            or_null_node.data = &queue_node;
+
+            // TODO test canceling getOrNull
+            errdefer {
+                _ = self.or_null_queue.remove(&or_null_node);
+                _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+                const need_dispatch = !self.getters.remove(&queue_node);
+                self.loop.cancelOnNextTick(&my_tick_node);
+                if (need_dispatch) {
+                    // oops we made the get_count incorrect for a period of time. fix by dispatching.
+                    _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+                    self.dispatch();
+                }
+            }
+
+            suspend |_| {
+                self.getters.put(&queue_node);
+                _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
+                self.or_null_queue.put(&or_null_node);
+
+                self.dispatch();
+            }
+            return result;
         }
 
         fn dispatch(self: *SelfChannel) void {
@@ -143,7 +244,15 @@ pub fn Channel(comptime T: type) type {
                             if (get_count == 0) break :one_dispatch;
 
                             const get_node = &self.getters.get().?.data;
-                            get_node.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len];
+                            switch (get_node.data) {
+                                GetNode.Data.Normal => |info| {
+                                    info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len];
+                                },
+                                GetNode.Data.OrNull => |info| {
+                                    _ = self.or_null_queue.remove(info.or_null);
+                                    info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len];
+                                },
+                            }
                             self.loop.onNextTick(get_node.tick_node);
                             self.buffer_len -= 1;
 
@@ -155,7 +264,15 @@ pub fn Channel(comptime T: type) type {
                             const get_node = &self.getters.get().?.data;
                             const put_node = &self.putters.get().?.data;
 
-                            get_node.ptr.* = put_node.data;
+                            switch (get_node.data) {
+                                GetNode.Data.Normal => |info| {
+                                    info.ptr.* = put_node.data;
+                                },
+                                GetNode.Data.OrNull => |info| {
+                                    _ = self.or_null_queue.remove(info.or_null);
+                                    info.ptr.* = put_node.data;
+                                },
+                            }
                             self.loop.onNextTick(get_node.tick_node);
                             self.loop.onNextTick(put_node.tick_node);
 
@@ -180,6 +297,16 @@ pub fn Channel(comptime T: type) type {
                     _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
                     _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
 
+                    // All the "get or null" functions should resume now.
+                    var remove_count: usize = 0;
+                    while (self.or_null_queue.get()) |or_null_node| {
+                        remove_count += @boolToInt(self.getters.remove(or_null_node.data));
+                        self.loop.onNextTick(or_null_node.data.data.tick_node);
+                    }
+                    if (remove_count != 0) {
+                        _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, remove_count, AtomicOrder.SeqCst);
+                    }
+
                     // clear need-dispatch flag
                     const need_dispatch = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
                     if (need_dispatch != 0) continue;
@@ -230,6 +357,15 @@ async fn testChannelGetter(loop: *Loop, channel: *Channel(i32)) void {
     const value2_promise = try async channel.get();
     const value2 = await value2_promise;
     assert(value2 == 4567);
+
+    const value3_promise = try async channel.getOrNull();
+    const value3 = await value3_promise;
+    assert(value3 == null);
+
+    const last_put = try async testPut(channel, 4444);
+    const value4 = await try async channel.getOrNull();
+    assert(value4.? == 4444);
+    await last_put;
 }
 
 async fn testChannelPutter(channel: *Channel(i32)) void {
@@ -237,3 +373,6 @@ async fn testChannelPutter(channel: *Channel(i32)) void {
     await (async channel.put(4567) catch @panic("out of memory"));
 }
 
+async fn testPut(channel: *Channel(i32), value: i32) void {
+    await (async channel.put(value) catch @panic("out of memory"));
+}
std/event/fs.zig
@@ -99,6 +99,7 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:
     }
 
     var req_node = RequestNode{
+        .prev = undefined,
         .next = undefined,
         .data = Request{
             .msg = Request.Msg{
@@ -111,6 +112,7 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data:
             },
             .finish = Request.Finish{
                 .TickNode = event.Loop.NextTickNode{
+                    .prev = undefined,
                     .next = undefined,
                     .data = my_handle,
                 },
@@ -148,6 +150,7 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [
     }
 
     var req_node = RequestNode{
+        .prev = undefined,
         .next = undefined,
         .data = Request{
             .msg = Request.Msg{
@@ -160,6 +163,7 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [
             },
             .finish = Request.Finish{
                 .TickNode = event.Loop.NextTickNode{
+                    .prev = undefined,
                     .next = undefined,
                     .data = my_handle,
                 },
@@ -186,6 +190,7 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.
     defer loop.allocator.free(path_with_null);
 
     var req_node = RequestNode{
+        .prev = undefined,
         .next = undefined,
         .data = Request{
             .msg = Request.Msg{
@@ -196,6 +201,7 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os.
             },
             .finish = Request.Finish{
                 .TickNode = event.Loop.NextTickNode{
+                    .prev = undefined,
                     .next = undefined,
                     .data = my_handle,
                 },
@@ -227,6 +233,7 @@ pub async fn openReadWrite(
     defer loop.allocator.free(path_with_null);
 
     var req_node = RequestNode{
+        .prev = undefined,
         .next = undefined,
         .data = Request{
             .msg = Request.Msg{
@@ -238,6 +245,7 @@ pub async fn openReadWrite(
             },
             .finish = Request.Finish{
                 .TickNode = event.Loop.NextTickNode{
+                    .prev = undefined,
                     .next = undefined,
                     .data = my_handle,
                 },
@@ -267,6 +275,7 @@ pub const CloseOperation = struct {
             .loop = loop,
             .have_fd = false,
             .close_req_node = RequestNode{
+                .prev = undefined,
                 .next = undefined,
                 .data = Request{
                     .msg = Request.Msg{
@@ -312,6 +321,7 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons
     defer loop.allocator.free(path_with_null);
 
     var req_node = RequestNode{
+        .prev = undefined,
         .next = undefined,
         .data = Request{
             .msg = Request.Msg{
@@ -324,6 +334,7 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons
             },
             .finish = Request.Finish{
                 .TickNode = event.Loop.NextTickNode{
+                    .prev = undefined,
                     .next = undefined,
                     .data = my_handle,
                 },
std/event/lock.zig
@@ -91,13 +91,16 @@ pub const Lock = struct {
     }
 
     pub async fn acquire(self: *Lock) Held {
-        suspend |handle| {
-            // TODO explicitly put this memory in the coroutine frame #1194
-            var my_tick_node = Loop.NextTickNode{
-                .data = handle,
-                .next = undefined,
-            };
+        // TODO explicitly put this memory in the coroutine frame #1194
+        var my_handle: promise = undefined;
+        suspend |p| {
+            my_handle = p;
+            resume p;
+        }
+        var my_tick_node = Loop.NextTickNode.init(my_handle);
 
+        errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire
+        suspend |_| {
             self.queue.put(&my_tick_node);
 
             // At this point, we are in the queue, so we might have already been resumed and this coroutine
@@ -170,6 +173,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void {
     }
     const handle1 = async lockRunner(lock) catch @panic("out of memory");
     var tick_node1 = Loop.NextTickNode{
+        .prev = undefined,
         .next = undefined,
         .data = handle1,
     };
@@ -177,6 +181,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void {
 
     const handle2 = async lockRunner(lock) catch @panic("out of memory");
     var tick_node2 = Loop.NextTickNode{
+        .prev = undefined,
         .next = undefined,
         .data = handle2,
     };
@@ -184,6 +189,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void {
 
     const handle3 = async lockRunner(lock) catch @panic("out of memory");
     var tick_node3 = Loop.NextTickNode{
+        .prev = undefined,
         .next = undefined,
         .data = handle3,
     };
std/event/loop.zig
@@ -120,6 +120,7 @@ pub const Loop = struct {
                 // we need another thread for the file system because Linux does not have an async
                 // file system I/O API.
                 self.os_data.fs_end_request = fs.RequestNode{
+                    .prev = undefined,
                     .next = undefined,
                     .data = fs.Request{
                         .msg = fs.Request.Msg.End,
@@ -206,6 +207,7 @@ pub const Loop = struct {
                                 .udata = @ptrToInt(&eventfd_node.data.base),
                             },
                         },
+                        .prev = undefined,
                         .next = undefined,
                     };
                     self.available_eventfd_resume_nodes.push(eventfd_node);
@@ -270,6 +272,7 @@ pub const Loop = struct {
                             // this one is for sending events
                             .completion_key = @ptrToInt(&eventfd_node.data.base),
                         },
+                        .prev = undefined,
                         .next = undefined,
                     };
                     self.available_eventfd_resume_nodes.push(eventfd_node);
@@ -422,6 +425,12 @@ pub const Loop = struct {
         self.dispatch();
     }
 
+    pub fn cancelOnNextTick(self: *Loop, node: *NextTickNode) void {
+        if (self.next_tick_queue.remove(node)) {
+            self.finishOneEvent();
+        }
+    }
+
     pub fn run(self: *Loop) void {
         self.finishOneEvent(); // the reference we start with
 
@@ -443,6 +452,7 @@ pub const Loop = struct {
                 suspend |p| {
                     handle.* = p;
                     var my_tick_node = Loop.NextTickNode{
+                        .prev = undefined,
                         .next = undefined,
                         .data = p,
                     };
@@ -464,6 +474,7 @@ pub const Loop = struct {
     pub async fn yield(self: *Loop) void {
         suspend |p| {
             var my_tick_node = Loop.NextTickNode{
+                .prev = undefined,
                 .next = undefined,
                 .data = p,
             };
std/event/rwlock.zig
@@ -101,6 +101,7 @@ pub const RwLock = struct {
             // TODO explicitly put this memory in the coroutine frame #1194
             var my_tick_node = Loop.NextTickNode{
                 .data = handle,
+                .prev = undefined,
                 .next = undefined,
             };
 
@@ -133,6 +134,7 @@ pub const RwLock = struct {
             // TODO explicitly put this memory in the coroutine frame #1194
             var my_tick_node = Loop.NextTickNode{
                 .data = handle,
+                .prev = undefined,
                 .next = undefined,
             };
 
std/event.zig
@@ -3,7 +3,7 @@ pub const Future = @import("event/future.zig").Future;
 pub const Group = @import("event/group.zig").Group;
 pub const Lock = @import("event/lock.zig").Lock;
 pub const Locked = @import("event/locked.zig").Locked;
-pub const RwLock = @import("event/rwlock.zig").Lock;
+pub const RwLock = @import("event/rwlock.zig").RwLock;
 pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
 pub const Loop = @import("event/loop.zig").Loop;
 pub const fs = @import("event/fs.zig");
std/index.zig
@@ -6,7 +6,6 @@ pub const Buffer = @import("buffer.zig").Buffer;
 pub const BufferOutStream = @import("buffer.zig").BufferOutStream;
 pub const HashMap = @import("hash_map.zig").HashMap;
 pub const LinkedList = @import("linked_list.zig").LinkedList;
-pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList;
 pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
 pub const DynLib = @import("dynamic_library.zig").DynLib;
 pub const Mutex = @import("mutex.zig").Mutex;
std/linked_list.zig
@@ -4,18 +4,8 @@ const assert = debug.assert;
 const mem = std.mem;
 const Allocator = mem.Allocator;
 
-/// Generic non-intrusive doubly linked list.
-pub fn LinkedList(comptime T: type) type {
-    return BaseLinkedList(T, void, "");
-}
-
-/// Generic intrusive doubly linked list.
-pub fn IntrusiveLinkedList(comptime ParentType: type, comptime field_name: []const u8) type {
-    return BaseLinkedList(void, ParentType, field_name);
-}
-
 /// Generic doubly linked list.
-fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_name: []const u8) type {
+pub fn LinkedList(comptime T: type) type {
     return struct {
         const Self = this;
 
@@ -25,23 +15,13 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
             next: ?*Node,
             data: T,
 
-            pub fn init(value: *const T) Node {
+            pub fn init(data: T) Node {
                 return Node{
                     .prev = null,
                     .next = null,
-                    .data = value.*,
+                    .data = data,
                 };
             }
-
-            pub fn initIntrusive() Node {
-                // TODO: when #678 is solved this can become `init`.
-                return Node.init({});
-            }
-
-            pub fn toData(node: *Node) *ParentType {
-                comptime assert(isIntrusive());
-                return @fieldParentPtr(ParentType, field_name, node);
-            }
         };
 
         first: ?*Node,
@@ -60,10 +40,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
             };
         }
 
-        fn isIntrusive() bool {
-            return ParentType != void or field_name.len != 0;
-        }
-
         /// Insert a new node after an existing one.
         ///
         /// Arguments:
@@ -192,7 +168,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
         /// Returns:
         ///     A pointer to the new node.
         pub fn allocateNode(list: *Self, allocator: *Allocator) !*Node {
-            comptime assert(!isIntrusive());
             return allocator.create(Node(undefined));
         }
 
@@ -202,7 +177,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
         ///     node: Pointer to the node to deallocate.
         ///     allocator: Dynamic memory allocator.
         pub fn destroyNode(list: *Self, node: *Node, allocator: *Allocator) void {
-            comptime assert(!isIntrusive());
             allocator.destroy(node);
         }
 
@@ -214,8 +188,7 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
         ///
         /// Returns:
         ///     A pointer to the new node.
-        pub fn createNode(list: *Self, data: *const T, allocator: *Allocator) !*Node {
-            comptime assert(!isIntrusive());
+        pub fn createNode(list: *Self, data: T, allocator: *Allocator) !*Node {
             var node = try list.allocateNode(allocator);
             node.* = Node.init(data);
             return node;
@@ -274,69 +247,3 @@ test "basic linked list test" {
     assert(list.last.?.data == 4);
     assert(list.len == 2);
 }
-
-const ElementList = IntrusiveLinkedList(Element, "link");
-const Element = struct {
-    value: u32,
-    link: IntrusiveLinkedList(Element, "link").Node,
-};
-
-test "basic intrusive linked list test" {
-    const allocator = debug.global_allocator;
-    var list = ElementList.init();
-
-    var one = Element{
-        .value = 1,
-        .link = ElementList.Node.initIntrusive(),
-    };
-    var two = Element{
-        .value = 2,
-        .link = ElementList.Node.initIntrusive(),
-    };
-    var three = Element{
-        .value = 3,
-        .link = ElementList.Node.initIntrusive(),
-    };
-    var four = Element{
-        .value = 4,
-        .link = ElementList.Node.initIntrusive(),
-    };
-    var five = Element{
-        .value = 5,
-        .link = ElementList.Node.initIntrusive(),
-    };
-
-    list.append(&two.link); // {2}
-    list.append(&five.link); // {2, 5}
-    list.prepend(&one.link); // {1, 2, 5}
-    list.insertBefore(&five.link, &four.link); // {1, 2, 4, 5}
-    list.insertAfter(&two.link, &three.link); // {1, 2, 3, 4, 5}
-
-    // Traverse forwards.
-    {
-        var it = list.first;
-        var index: u32 = 1;
-        while (it) |node| : (it = node.next) {
-            assert(node.toData().value == index);
-            index += 1;
-        }
-    }
-
-    // Traverse backwards.
-    {
-        var it = list.last;
-        var index: u32 = 1;
-        while (it) |node| : (it = node.prev) {
-            assert(node.toData().value == (6 - index));
-            index += 1;
-        }
-    }
-
-    var first = list.popFirst(); // {2, 3, 4, 5}
-    var last = list.pop(); // {2, 3, 4}
-    list.remove(&three.link); // {2, 4}
-
-    assert(list.first.?.toData().value == 2);
-    assert(list.last.?.toData().value == 4);
-    assert(list.len == 2);
-}