forked from buildfarm/buildfarm
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Persistent Workers as an execution path (buildfarm#1260)
Followup to buildfarm#1195 Add a new execution pathway in worker/Executor.java to use persistent workers via PersistentExecutor, like DockerExecutor. Mostly unchanged from the form we used to experiment back at Twitter, but now with tests. Co-authored-by: Shane Delmore [email protected]
- Loading branch information
Showing
24 changed files
with
2,043 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
java_library( | ||
name = "persistent", | ||
srcs = glob(["*.java"]), | ||
plugins = ["//src/main/java/build/buildfarm/common:lombok"], | ||
visibility = ["//visibility:public"], | ||
deps = [ | ||
"//persistentworkers/src/main/java/persistent/bazel:bazel-persistent-workers", | ||
"//persistentworkers/src/main/java/persistent/common:persistent-common", | ||
"//persistentworkers/src/main/java/persistent/common/util", | ||
"//persistentworkers/src/main/protobuf:worker_protocol_java_proto", | ||
"//src/main/java/build/buildfarm/common", | ||
"//src/main/java/build/buildfarm/worker/resources", | ||
"//src/main/java/build/buildfarm/worker/util", | ||
"//src/main/protobuf:build_buildfarm_v1test_buildfarm_java_proto", | ||
"@maven//:com_google_api_grpc_proto_google_common_protos", | ||
"@maven//:com_google_guava_guava", | ||
"@maven//:com_google_protobuf_protobuf_java", | ||
"@maven//:com_google_protobuf_protobuf_java_util", | ||
"@maven//:commons_io_commons_io", | ||
"@maven//:io_grpc_grpc_api", | ||
"@maven//:io_grpc_grpc_context", | ||
"@maven//:io_grpc_grpc_core", | ||
"@maven//:io_grpc_grpc_netty", | ||
"@maven//:io_grpc_grpc_protobuf", | ||
"@maven//:io_grpc_grpc_stub", | ||
"@maven//:io_prometheus_simpleclient", | ||
"@maven//:org_apache_commons_commons_compress", | ||
"@maven//:org_jetbrains_annotations", | ||
"@maven//:org_projectlombok_lombok", | ||
], | ||
) |
171 changes: 171 additions & 0 deletions
171
src/main/java/build/buildfarm/worker/persistent/FileAccessUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// Copyright 2023 The Bazel Authors. All rights reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package build.buildfarm.worker.persistent; | ||
|
||
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; | ||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; | ||
|
||
import com.google.common.collect.ImmutableSet; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.attribute.PosixFilePermission; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.Supplier; | ||
import lombok.extern.java.Log; | ||
|
||
/** | ||
* Utility for concurrent move/copy of files Can be extended in the future to (sym)linking if we | ||
* need performance | ||
*/ | ||
@Log | ||
public final class FileAccessUtils { | ||
// singleton class with only static methods | ||
private FileAccessUtils() {} | ||
|
||
public static Path addPosixOwnerWrite(Path absPath) throws IOException { | ||
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(absPath); | ||
|
||
ImmutableSet<PosixFilePermission> permsWithWrite = | ||
ImmutableSet.<PosixFilePermission>builder() | ||
.addAll(perms) | ||
.add(PosixFilePermission.OWNER_WRITE) | ||
.build(); | ||
|
||
return Files.setAttribute(absPath, "posix:permissions", permsWithWrite); | ||
} | ||
|
||
private static final ConcurrentHashMap<Path, PathLock> fileLocks = new ConcurrentHashMap<>(); | ||
|
||
// Used here as a simple lock for locking "files" (paths) | ||
private static class PathLock { | ||
// Not used elsewhere | ||
private PathLock() {} | ||
} | ||
|
||
/** | ||
* Copies a file, creating necessary directories, replacing existing files. The resulting file is | ||
* set to be writeable, and we throw if we cannot set that. Thread-safe (within a process) against | ||
* writes to the same path. | ||
* | ||
* @param from | ||
* @param to | ||
* @throws IOException | ||
*/ | ||
public static void copyFile(Path from, Path to) throws IOException { | ||
Path absTo = to.toAbsolutePath(); | ||
log.finer("copyFile: " + from + " to " + absTo); | ||
if (!Files.exists(from)) { | ||
throw new IOException("copyFile: source file doesn't exist: " + from); | ||
} | ||
IOException ioException = | ||
writeFileSafe( | ||
to, | ||
() -> { | ||
try { | ||
Files.copy(from, absTo, REPLACE_EXISTING, COPY_ATTRIBUTES); | ||
addPosixOwnerWrite(absTo); | ||
return null; | ||
} catch (IOException e) { | ||
return new IOException("copyFile() could not set writeable: " + absTo, e); | ||
} | ||
}); | ||
if (ioException != null) { | ||
throw ioException; | ||
} | ||
} | ||
|
||
/** | ||
* Moves a file, creating necessary directories, replacing existing files. The resulting file is | ||
* set to be writeable, and we throw if we cannot set that. Thread-safe against writes to the same | ||
* path. | ||
* | ||
* @param from | ||
* @param to | ||
* @throws IOException | ||
*/ | ||
public static void moveFile(Path from, Path to) throws IOException { | ||
Path absTo = to.toAbsolutePath(); | ||
log.finer("moveFile: " + from + " to " + absTo); | ||
if (!Files.exists(from)) { | ||
throw new IOException("moveFile: source file doesn't exist: " + from); | ||
} | ||
IOException ioException = | ||
writeFileSafe( | ||
absTo, | ||
() -> { | ||
try { | ||
Files.move(from, absTo, REPLACE_EXISTING); | ||
addPosixOwnerWrite(absTo); | ||
return null; | ||
} catch (IOException e) { | ||
return new IOException("copyFile() could not set writeable: " + absTo, e); | ||
} | ||
}); | ||
if (ioException != null) { | ||
throw ioException; | ||
} | ||
} | ||
|
||
/** | ||
* Deletes a file; Thread-safe against writes to the same path. | ||
* | ||
* @param toDelete | ||
* @throws IOException | ||
*/ | ||
public static void deleteFileIfExists(Path toDelete) throws IOException { | ||
Path absTo = toDelete.toAbsolutePath(); | ||
PathLock toLock = fileLock(absTo); | ||
synchronized (toLock) { | ||
try { | ||
Files.deleteIfExists(absTo); | ||
} finally { | ||
fileLocks.remove(absTo); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Thread-safe (not multi-process-safe) wrapper for locking paths before a write operation. | ||
* | ||
* <p>This method will create necessary parent directories. | ||
* | ||
* <p>It is up to the write operation to specify whether or not to overwrite existing files. | ||
*/ | ||
@SuppressWarnings("PMD.UnnecessaryLocalBeforeReturn") | ||
private static IOException writeFileSafe(Path absTo, Supplier<IOException> writeOp) { | ||
PathLock toLock = fileLock(absTo); | ||
synchronized (toLock) { | ||
try { | ||
// If 'absTo' is a symlink, checks if its target file exists | ||
Files.createDirectories(absTo.getParent()); | ||
return writeOp.get(); | ||
} catch (IOException e) { | ||
// PMD will complain about UnnecessaryLocalBeforeReturn | ||
// In this case, it is necessary to catch the exception | ||
return e; | ||
} finally { | ||
// Clean up to prevent too many locks. | ||
fileLocks.remove(absTo); | ||
} | ||
} | ||
} | ||
|
||
// "Logical" file lock | ||
private static PathLock fileLock(Path writeTo) { | ||
return fileLocks.computeIfAbsent(writeTo, k -> new PathLock()); | ||
} | ||
} |
Oops, something went wrong.