| ... | ... | @@ -38,15 +38,12 @@ const assert = std.debug.assert; |
| 38 | 38 | const ascii = std.ascii; |
| 39 | 39 | const Allocator = std.mem.Allocator; |
| 40 | 40 | const Cache = std.Build.Cache; |
| 41 | | const ThreadPool = std.Thread.Pool; |
| 42 | | const WaitGroup = std.Thread.WaitGroup; |
| 43 | 41 | const git = @import("Fetch/git.zig"); |
| 44 | 42 | const Package = @import("../Package.zig"); |
| 45 | 43 | const Manifest = Package.Manifest; |
| 46 | 44 | const ErrorBundle = std.zig.ErrorBundle; |
| 47 | 45 | |
| 48 | 46 | arena: std.heap.ArenaAllocator, |
| 49 | | io: Io, |
| 50 | 47 | location: Location, |
| 51 | 48 | location_tok: std.zig.Ast.TokenIndex, |
| 52 | 49 | hash_tok: std.zig.Ast.OptionalTokenIndex, |
| ... | ... | @@ -104,7 +101,8 @@ pub const LazyStatus = enum { |
| 104 | 101 | |
| 105 | 102 | /// Contains shared state among all `Fetch` tasks. |
| 106 | 103 | pub const JobQueue = struct { |
| 107 | | mutex: std.Thread.Mutex = .{}, |
| 104 | io: Io, |
| 105 | mutex: Io.Mutex = .init, |
| 108 | 106 | /// It's an array hash map so that it can be sorted before rendering the |
| 109 | 107 | /// dependencies.zig source file. |
| 110 | 108 | /// Protected by `mutex`. |
| ... | ... | @@ -115,8 +113,7 @@ pub const JobQueue = struct { |
| 115 | 113 | all_fetches: std.ArrayList(*Fetch) = .empty, |
| 116 | 114 | |
| 117 | 115 | http_client: *std.http.Client, |
| 118 | | thread_pool: *ThreadPool, |
| 119 | | wait_group: WaitGroup = .{}, |
| 116 | group: Io.Group = .init, |
| 120 | 117 | global_cache: Cache.Directory, |
| 121 | 118 | /// If true then, no fetching occurs, and: |
| 122 | 119 | /// * The `global_cache` directory is assumed to be the direct parent |
| ... | ... | @@ -320,13 +317,14 @@ pub const Location = union(enum) { |
| 320 | 317 | |
| 321 | 318 | pub const RunError = error{ |
| 322 | 319 | OutOfMemory, |
| 320 | Canceled, |
| 323 | 321 | /// This error code is intended to be handled by inspecting the |
| 324 | 322 | /// `error_bundle` field. |
| 325 | 323 | FetchFailed, |
| 326 | 324 | }; |
| 327 | 325 | |
| 328 | 326 | pub fn run(f: *Fetch) RunError!void { |
| 329 | | const io = f.io; |
| 327 | const io = f.job_queue.io; |
| 330 | 328 | const eb = &f.error_bundle; |
| 331 | 329 | const arena = f.arena.allocator(); |
| 332 | 330 | const gpa = f.arena.child_allocator; |
| ... | ... | @@ -488,7 +486,7 @@ fn runResource( |
| 488 | 486 | resource: *Resource, |
| 489 | 487 | remote_hash: ?Package.Hash, |
| 490 | 488 | ) RunError!void { |
| 491 | | const io = f.io; |
| 489 | const io = f.job_queue.io; |
| 492 | 490 | defer resource.deinit(io); |
| 493 | 491 | const arena = f.arena.allocator(); |
| 494 | 492 | const eb = &f.error_bundle; |
| ... | ... | @@ -702,7 +700,8 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { |
| 702 | 700 | } |
| 703 | 701 | |
| 704 | 702 | fn queueJobsForDeps(f: *Fetch) RunError!void { |
| 705 | | const io = f.io; |
| 703 | const io = f.job_queue.io; |
| 704 | |
| 706 | 705 | assert(f.job_queue.recursive); |
| 707 | 706 | |
| 708 | 707 | // If the package does not have a build.zig.zon file then there are no dependencies. |
| ... | ... | @@ -722,8 +721,8 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { |
| 722 | 721 | const prog_names = try parent_arena.alloc([]const u8, deps.len); |
| 723 | 722 | var new_fetch_index: usize = 0; |
| 724 | 723 | |
| 725 | | f.job_queue.mutex.lock(); |
| 726 | | defer f.job_queue.mutex.unlock(); |
| 724 | try f.job_queue.mutex.lock(io); |
| 725 | defer f.job_queue.mutex.unlock(io); |
| 727 | 726 | |
| 728 | 727 | try f.job_queue.all_fetches.ensureUnusedCapacity(gpa, new_fetches.len); |
| 729 | 728 | try f.job_queue.table.ensureUnusedCapacity(gpa, @intCast(new_fetches.len)); |
| ... | ... | @@ -792,7 +791,6 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { |
| 792 | 791 | f.job_queue.all_fetches.appendAssumeCapacity(new_fetch); |
| 793 | 792 | } |
| 794 | 793 | new_fetch.* = .{ |
| 795 | | .io = io, |
| 796 | 794 | .arena = std.heap.ArenaAllocator.init(gpa), |
| 797 | 795 | .location = location, |
| 798 | 796 | .location_tok = dep.location_tok, |
| ... | ... | @@ -830,11 +828,9 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { |
| 830 | 828 | break :nf .{ new_fetches[0..new_fetch_index], prog_names[0..new_fetch_index] }; |
| 831 | 829 | }; |
| 832 | 830 | |
| 833 | | // Now it's time to give tasks to the thread pool. |
| 834 | | const thread_pool = f.job_queue.thread_pool; |
| 835 | | |
| 831 | // Now it's time to dispatch tasks. |
| 836 | 832 | for (new_fetches, prog_names) |*new_fetch, prog_name| { |
| 837 | | thread_pool.spawnWg(&f.job_queue.wait_group, workerRun, .{ new_fetch, prog_name }); |
| 833 | f.job_queue.group.async(io, workerRun, .{ new_fetch, prog_name }); |
| 838 | 834 | } |
| 839 | 835 | } |
| 840 | 836 | |
| ... | ... | @@ -848,6 +844,7 @@ pub fn workerRun(f: *Fetch, prog_name: []const u8) void { |
| 848 | 844 | |
| 849 | 845 | run(f) catch |err| switch (err) { |
| 850 | 846 | error.OutOfMemory => f.oom_flag = true, |
| 847 | error.Canceled => {}, |
| 851 | 848 | error.FetchFailed => { |
| 852 | 849 | // Nothing to do because the errors are already reported in `error_bundle`, |
| 853 | 850 | // and a reference is kept to the `Fetch` task inside `all_fetches`. |
| ... | ... | @@ -992,7 +989,7 @@ const FileType = enum { |
| 992 | 989 | const init_resource_buffer_size = git.Packet.max_data_length; |
| 993 | 990 | |
| 994 | 991 | fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u8) RunError!void { |
| 995 | | const io = f.io; |
| 992 | const io = f.job_queue.io; |
| 996 | 993 | const arena = f.arena.allocator(); |
| 997 | 994 | const eb = &f.error_bundle; |
| 998 | 995 | |
| ... | ... | @@ -1281,12 +1278,16 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!Unpack |
| 1281 | 1278 | return res; |
| 1282 | 1279 | } |
| 1283 | 1280 | |
| 1284 | | fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) error{ ReadFailed, OutOfMemory, FetchFailed }!UnpackResult { |
| 1281 | fn unzip( |
| 1282 | f: *Fetch, |
| 1283 | out_dir: fs.Dir, |
| 1284 | reader: *Io.Reader, |
| 1285 | ) error{ ReadFailed, OutOfMemory, Canceled, FetchFailed }!UnpackResult { |
| 1285 | 1286 | // We write the entire contents to a file first because zip files |
| 1286 | 1287 | // must be processed back to front and they could be too large to |
| 1287 | 1288 | // load into memory. |
| 1288 | 1289 | |
| 1289 | | const io = f.io; |
| 1290 | const io = f.job_queue.io; |
| 1290 | 1291 | const cache_root = f.job_queue.global_cache; |
| 1291 | 1292 | const prefix = "tmp/"; |
| 1292 | 1293 | const suffix = ".zip"; |
| ... | ... | @@ -1306,6 +1307,7 @@ fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) error{ ReadFailed, OutO |
| 1306 | 1307 | .read = true, |
| 1307 | 1308 | }) catch |err| switch (err) { |
| 1308 | 1309 | error.PathAlreadyExists => continue, |
| 1310 | error.Canceled => return error.Canceled, |
| 1309 | 1311 | else => |e| return f.fail( |
| 1310 | 1312 | f.location_tok, |
| 1311 | 1313 | try eb.printString("failed to create temporary zip file: {t}", .{e}), |
| ... | ... | @@ -1348,7 +1350,7 @@ fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) error{ ReadFailed, OutO |
| 1348 | 1350 | } |
| 1349 | 1351 | |
| 1350 | 1352 | fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult { |
| 1351 | | const io = f.io; |
| 1353 | const io = f.job_queue.io; |
| 1352 | 1354 | const arena = f.arena.allocator(); |
| 1353 | 1355 | // TODO don't try to get a gpa from an arena. expose this dependency higher up |
| 1354 | 1356 | // because the backing of arena could be page allocator |
| ... | ... | @@ -1486,11 +1488,11 @@ const ComputedHash = struct { |
| 1486 | 1488 | /// hashed* and must not be present on the file system when calling this |
| 1487 | 1489 | /// function. |
| 1488 | 1490 | fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!ComputedHash { |
| 1491 | const io = f.job_queue.io; |
| 1489 | 1492 | // All the path name strings need to be in memory for sorting. |
| 1490 | 1493 | const arena = f.arena.allocator(); |
| 1491 | 1494 | const gpa = f.arena.child_allocator; |
| 1492 | 1495 | const eb = &f.error_bundle; |
| 1493 | | const thread_pool = f.job_queue.thread_pool; |
| 1494 | 1496 | const root_dir = pkg_path.root_dir.handle; |
| 1495 | 1497 | |
| 1496 | 1498 | // Collect all files, recursively, then sort. |
| ... | ... | @@ -1514,10 +1516,8 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute |
| 1514 | 1516 | { |
| 1515 | 1517 | // The final hash will be a hash of each file hashed independently. This |
| 1516 | 1518 | // allows hashing in parallel. |
| 1517 | | var wait_group: WaitGroup = .{}; |
| 1518 | | // `computeHash` is called from a worker thread so there must not be |
| 1519 | | // any waiting without working or a deadlock could occur. |
| 1520 | | defer thread_pool.waitAndWork(&wait_group); |
| 1519 | var group: Io.Group = .init; |
| 1520 | defer group.wait(io); |
| 1521 | 1521 | |
| 1522 | 1522 | while (walker.next() catch |err| { |
| 1523 | 1523 | try eb.addRootErrorMessage(.{ .msg = try eb.printString( |
| ... | ... | @@ -1542,7 +1542,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute |
| 1542 | 1542 | .fs_path = fs_path, |
| 1543 | 1543 | .failure = undefined, // to be populated by the worker |
| 1544 | 1544 | }; |
| 1545 | | thread_pool.spawnWg(&wait_group, workerDeleteFile, .{ root_dir, deleted_file }); |
| 1545 | group.async(io, workerDeleteFile, .{ root_dir, deleted_file }); |
| 1546 | 1546 | try deleted_files.append(deleted_file); |
| 1547 | 1547 | continue; |
| 1548 | 1548 | } |
| ... | ... | @@ -1570,7 +1570,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute |
| 1570 | 1570 | .failure = undefined, // to be populated by the worker |
| 1571 | 1571 | .size = undefined, // to be populated by the worker |
| 1572 | 1572 | }; |
| 1573 | | thread_pool.spawnWg(&wait_group, workerHashFile, .{ root_dir, hashed_file }); |
| 1573 | group.async(io, workerHashFile, .{ root_dir, hashed_file }); |
| 1574 | 1574 | try all_files.append(hashed_file); |
| 1575 | 1575 | } |
| 1576 | 1576 | } |
| ... | ... | @@ -2241,7 +2241,6 @@ fn saveEmbedFile(comptime tarball_name: []const u8, dir: fs.Dir) !void { |
| 2241 | 2241 | |
| 2242 | 2242 | // Builds Fetch with required dependencies, clears dependencies on deinit(). |
| 2243 | 2243 | const TestFetchBuilder = struct { |
| 2244 | | thread_pool: ThreadPool, |
| 2245 | 2244 | http_client: std.http.Client, |
| 2246 | 2245 | global_cache_directory: Cache.Directory, |
| 2247 | 2246 | job_queue: Fetch.JobQueue, |
| ... | ... | @@ -2256,13 +2255,12 @@ const TestFetchBuilder = struct { |
| 2256 | 2255 | ) !*Fetch { |
| 2257 | 2256 | const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{}); |
| 2258 | 2257 | |
| 2259 | | try self.thread_pool.init(.{ .allocator = allocator }); |
| 2260 | 2258 | self.http_client = .{ .allocator = allocator, .io = io }; |
| 2261 | 2259 | self.global_cache_directory = .{ .handle = cache_dir, .path = null }; |
| 2262 | 2260 | |
| 2263 | 2261 | self.job_queue = .{ |
| 2262 | .io = io, |
| 2264 | 2263 | .http_client = &self.http_client, |
| 2265 | | .thread_pool = &self.thread_pool, |
| 2266 | 2264 | .global_cache = self.global_cache_directory, |
| 2267 | 2265 | .recursive = false, |
| 2268 | 2266 | .read_only = false, |
| ... | ... | @@ -2273,7 +2271,6 @@ const TestFetchBuilder = struct { |
| 2273 | 2271 | |
| 2274 | 2272 | self.fetch = .{ |
| 2275 | 2273 | .arena = std.heap.ArenaAllocator.init(allocator), |
| 2276 | | .io = io, |
| 2277 | 2274 | .location = .{ .path_or_url = path_or_url }, |
| 2278 | 2275 | .location_tok = 0, |
| 2279 | 2276 | .hash_tok = .none, |
| ... | ... | @@ -2309,7 +2306,6 @@ const TestFetchBuilder = struct { |
| 2309 | 2306 | self.fetch.prog_node.end(); |
| 2310 | 2307 | self.global_cache_directory.handle.close(); |
| 2311 | 2308 | self.http_client.deinit(); |
| 2312 | | self.thread_pool.deinit(); |
| 2313 | 2309 | } |
| 2314 | 2310 | |
| 2315 | 2311 | fn packageDir(self: *TestFetchBuilder) !fs.Dir { |