| ... | @@ -654,63 +654,61 @@ test "realpath" { | ... | @@ -654,63 +654,61 @@ test "realpath" { |
| 654 | testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf)); | 654 | testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf)); |
| 655 | } | 655 | } |
| 656 | | 656 | |
| 657 | const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.ns_per_ms; | | |
| 658 | | | |
| 659 | test "open file with exclusive nonblocking lock twice" { | 657 | test "open file with exclusive nonblocking lock twice" { |
| 660 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 658 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 661 | | 659 | |
| 662 | // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 | 660 | // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 |
| 663 | if (builtin.os.tag == .freebsd) return error.SkipZigTest; | 661 | if (builtin.os.tag == .freebsd) return error.SkipZigTest; |
| 664 | | 662 | |
| 665 | const dir = fs.cwd(); | | |
| 666 | const filename = "file_nonblocking_lock_test.txt"; | 663 | const filename = "file_nonblocking_lock_test.txt"; |
| 667 | | 664 | |
| 668 | const file1 = try dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); | 665 | var tmp = tmpDir(.{}); |
| | 666 | defer tmp.cleanup(); |
| | 667 | |
| | 668 | const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); |
| 669 | defer file1.close(); | 669 | defer file1.close(); |
| 670 | | 670 | |
| 671 | const file2 = dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); | 671 | const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); |
| 672 | std.debug.assert(std.meta.eql(file2, error.WouldBlock)); | 672 | testing.expectError(error.WouldBlock, file2); |
| | 673 | } |
| 673 | | 674 | |
| 674 | dir.deleteFile(filename) catch |err| switch (err) { | 675 | test "open file with shared and exclusive nonblocking lock" { |
| 675 | error.FileNotFound => {}, | 676 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 676 | else => return err, | 677 | |
| 677 | }; | 678 | // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 |
| | 679 | if (builtin.os.tag == .freebsd) return error.SkipZigTest; |
| | 680 | |
| | 681 | const filename = "file_nonblocking_lock_test.txt"; |
| | 682 | |
| | 683 | var tmp = tmpDir(.{}); |
| | 684 | defer tmp.cleanup(); |
| | 685 | |
| | 686 | const file1 = try tmp.dir.createFile(filename, .{ .lock = .Shared, .lock_nonblocking = true }); |
| | 687 | defer file1.close(); |
| | 688 | |
| | 689 | const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); |
| | 690 | testing.expectError(error.WouldBlock, file2); |
| 678 | } | 691 | } |
| 679 | | 692 | |
| 680 | test "open file with lock twice, make sure it wasn't open at the same time" { | 693 | test "open file with exclusive and shared nonblocking lock" { |
| 681 | if (builtin.single_threaded) return error.SkipZigTest; | 694 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 682 | | 695 | |
| 683 | if (std.io.is_async) { | 696 | // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 |
| 684 | // This test starts its own threads and is not compatible with async I/O. | 697 | if (builtin.os.tag == .freebsd) return error.SkipZigTest; |
| 685 | return error.SkipZigTest; | | |
| 686 | } | | |
| 687 | | 698 | |
| 688 | const filename = "file_lock_test.txt"; | 699 | const filename = "file_nonblocking_lock_test.txt"; |
| 689 | var contexts = [_]FileLockTestContext{ | | |
| 690 | .{ .filename = filename, .create = true, .lock = .Exclusive }, | | |
| 691 | .{ .filename = filename, .create = true, .lock = .Exclusive }, | | |
| 692 | }; | | |
| 693 | try run_lock_file_test(&contexts); | | |
| 694 | | | |
| 695 | // Check for an error | | |
| 696 | var was_error = false; | | |
| 697 | for (contexts) |context, idx| { | | |
| 698 | if (context.err) |err| { | | |
| 699 | was_error = true; | | |
| 700 | std.debug.warn("\nError in context {}: {}\n", .{ idx, err }); | | |
| 701 | } | | |
| 702 | } | | |
| 703 | if (was_error) builtin.panic("There was an error in contexts", null); | | |
| 704 | | 700 | |
| 705 | std.debug.assert(!contexts[0].overlaps(&contexts[1])); | 701 | var tmp = tmpDir(.{}); |
| | 702 | defer tmp.cleanup(); |
| 706 | | 703 | |
| 707 | fs.cwd().deleteFile(filename) catch |err| switch (err) { | 704 | const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); |
| 708 | error.FileNotFound => {}, | 705 | defer file1.close(); |
| 709 | else => return err, | 706 | |
| 710 | }; | 707 | const file2 = tmp.dir.createFile(filename, .{ .lock = .Shared, .lock_nonblocking = true }); |
| | 708 | testing.expectError(error.WouldBlock, file2); |
| 711 | } | 709 | } |
| 712 | | 710 | |
| 713 | test "create file, lock and read from multiple process at once" { | 711 | test "open file with exclusive lock twice, make sure it waits" { |
| 714 | if (builtin.single_threaded) return error.SkipZigTest; | 712 | if (builtin.single_threaded) return error.SkipZigTest; |
| 715 | | 713 | |
| 716 | if (std.io.is_async) { | 714 | if (std.io.is_async) { |
| ... | @@ -718,46 +716,37 @@ test "create file, lock and read from multiple process at once" { | ... | @@ -718,46 +716,37 @@ test "create file, lock and read from multiple process at once" { |
| 718 | return error.SkipZigTest; | 716 | return error.SkipZigTest; |
| 719 | } | 717 | } |
| 720 | | 718 | |
| 721 | if (true) { | 719 | const filename = "file_lock_test.txt"; |
| 722 | // https://github.com/ziglang/zig/issues/5006 | | |
| 723 | return error.SkipZigTest; | | |
| 724 | } | | |
| 725 | | 720 | |
| 726 | const filename = "file_read_lock_test.txt"; | 721 | var tmp = tmpDir(.{}); |
| 727 | const filedata = "Hello, world!\n"; | 722 | defer tmp.cleanup(); |
| 728 | | 723 | |
| 729 | try fs.cwd().writeFile(filename, filedata); | 724 | const file = try tmp.dir.createFile(filename, .{ .lock = .Exclusive }); |
| | 725 | errdefer file.close(); |
| 730 | | 726 | |
| 731 | var contexts = [_]FileLockTestContext{ | 727 | const S = struct { |
| 732 | .{ .filename = filename, .create = false, .lock = .Shared }, | 728 | const C = struct { dir: *fs.Dir, evt: *std.ResetEvent }; |
| 733 | .{ .filename = filename, .create = false, .lock = .Shared }, | 729 | fn checkFn(ctx: C) !void { |
| 734 | .{ .filename = filename, .create = false, .lock = .Exclusive }, | 730 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive }); |
| | 731 | defer file1.close(); |
| | 732 | ctx.evt.set(); |
| | 733 | } |
| 735 | }; | 734 | }; |
| 736 | | 735 | |
| 737 | try run_lock_file_test(&contexts); | 736 | var evt = std.ResetEvent.init(); |
| | 737 | defer evt.deinit(); |
| 738 | | 738 | |
| 739 | var was_error = false; | 739 | const t = try std.Thread.spawn(S.C{ .dir = &tmp.dir, .evt = &evt }, S.checkFn); |
| 740 | for (contexts) |context, idx| { | 740 | defer t.wait(); |
| 741 | if (context.err) |err| { | | |
| 742 | was_error = true; | | |
| 743 | std.debug.warn("\nError in context {}: {}\n", .{ idx, err }); | | |
| 744 | } | | |
| 745 | } | | |
| 746 | if (was_error) builtin.panic("There was an error in contexts", null); | | |
| 747 | | 741 | |
| 748 | std.debug.assert(contexts[0].overlaps(&contexts[1])); | 742 | const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms; |
| 749 | std.debug.assert(!contexts[2].overlaps(&contexts[0])); | | |
| 750 | std.debug.assert(!contexts[2].overlaps(&contexts[1])); | | |
| 751 | if (contexts[0].bytes_read.? != filedata.len) { | | |
| 752 | std.debug.warn("\n bytes_read: {}, expected: {} \n", .{ contexts[0].bytes_read, filedata.len }); | | |
| 753 | } | | |
| 754 | std.debug.assert(contexts[0].bytes_read.? == filedata.len); | | |
| 755 | std.debug.assert(contexts[1].bytes_read.? == filedata.len); | | |
| 756 | | 743 | |
| 757 | fs.cwd().deleteFile(filename) catch |err| switch (err) { | 744 | std.time.sleep(SLEEP_TIMEOUT_NS); |
| 758 | error.FileNotFound => {}, | 745 | // Check that createFile is still waiting for the lock to be released. |
| 759 | else => return err, | 746 | testing.expect(!evt.isSet()); |
| 760 | }; | 747 | file.close(); |
| | 748 | // Generous timeout to avoid failures on heavily loaded systems. |
| | 749 | try evt.timedWait(SLEEP_TIMEOUT_NS); |
| 761 | } | 750 | } |
| 762 | | 751 | |
| 763 | test "open file with exclusive nonblocking lock twice (absolute paths)" { | 752 | test "open file with exclusive nonblocking lock twice (absolute paths)" { |
| ... | @@ -780,71 +769,3 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { | ... | @@ -780,71 +769,3 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { |
| 780 | | 769 | |
| 781 | try fs.deleteFileAbsolute(filename); | 770 | try fs.deleteFileAbsolute(filename); |
| 782 | } | 771 | } |
| 783 | | | |
| 784 | const FileLockTestContext = struct { | | |
| 785 | filename: []const u8, | | |
| 786 | pid: if (builtin.os.tag == .windows) ?void else ?std.os.pid_t = null, | | |
| 787 | | | |
| 788 | // use file.createFile | | |
| 789 | create: bool, | | |
| 790 | // the type of lock to use | | |
| 791 | lock: File.Lock, | | |
| 792 | | | |
| 793 | // Output variables | | |
| 794 | err: ?(File.OpenError || std.os.ReadError) = null, | | |
| 795 | start_time: i64 = 0, | | |
| 796 | end_time: i64 = 0, | | |
| 797 | bytes_read: ?usize = null, | | |
| 798 | | | |
| 799 | fn overlaps(self: *const @This(), other: *const @This()) bool { | | |
| 800 | return (self.start_time < other.end_time) and (self.end_time > other.start_time); | | |
| 801 | } | | |
| 802 | | | |
| 803 | fn run(ctx: *@This()) void { | | |
| 804 | var file: File = undefined; | | |
| 805 | if (ctx.create) { | | |
| 806 | file = fs.cwd().createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { | | |
| 807 | ctx.err = err; | | |
| 808 | return; | | |
| 809 | }; | | |
| 810 | } else { | | |
| 811 | file = fs.cwd().openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { | | |
| 812 | ctx.err = err; | | |
| 813 | return; | | |
| 814 | }; | | |
| 815 | } | | |
| 816 | defer file.close(); | | |
| 817 | | | |
| 818 | ctx.start_time = std.time.milliTimestamp(); | | |
| 819 | | | |
| 820 | if (!ctx.create) { | | |
| 821 | var buffer: [100]u8 = undefined; | | |
| 822 | ctx.bytes_read = 0; | | |
| 823 | while (true) { | | |
| 824 | const amt = file.read(buffer[0..]) catch |err| { | | |
| 825 | ctx.err = err; | | |
| 826 | return; | | |
| 827 | }; | | |
| 828 | if (amt == 0) break; | | |
| 829 | ctx.bytes_read.? += amt; | | |
| 830 | } | | |
| 831 | } | | |
| 832 | | | |
| 833 | std.time.sleep(FILE_LOCK_TEST_SLEEP_TIME); | | |
| 834 | | | |
| 835 | ctx.end_time = std.time.milliTimestamp(); | | |
| 836 | } | | |
| 837 | }; | | |
| 838 | | | |
| 839 | fn run_lock_file_test(contexts: []FileLockTestContext) !void { | | |
| 840 | var threads = std.ArrayList(*std.Thread).init(testing.allocator); | | |
| 841 | defer { | | |
| 842 | for (threads.items) |thread| { | | |
| 843 | thread.wait(); | | |
| 844 | } | | |
| 845 | threads.deinit(); | | |
| 846 | } | | |
| 847 | for (contexts) |*ctx, idx| { | | |
| 848 | try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run)); | | |
| 849 | } | | |
| 850 | } | | |