Worker ổn định có thể giúp bản dựng của bạn nhanh hơn. Nếu bạn có các hành động lặp lại trong bản dựng gây ra chi phí khởi động cao hoặc hưởng lợi từ việc lưu nhiều hành động vào bộ nhớ đệm, bạn nên triển khai phương thức lưu trữ cố định worker thực hiện các hành động này.
Máy chủ Bazel giao tiếp với worker bằng stdin
/stdout
. Nó
hỗ trợ sử dụng vùng đệm giao thức hoặc chuỗi JSON.
Quy trình triển khai trình thực thi gồm hai phần:
- worker (nhân viên).
- Quy tắc sử dụng worker.
Tạo dựng nhân viên
Một worker ổn định đáp ứng được một số yêu cầu:
- Có nội dung
WorkRequests
từ
stdin
. - Viết
WorkResponses
(và chỉ
WorkResponse
) đối vớistdout
. - Phương thức này chấp nhận cờ
--persistent_worker
. Trình bao bọc phải nhận ra Cờ hiệu dòng lệnh--persistent_worker
và chỉ tự tạo sự cố định nếu cờ đó đã được truyền, nếu không phải thực hiện biên dịch một lần và thoát.
Nếu chương trình của bạn đáp ứng những yêu cầu này, bạn có thể sử dụng chương trình này làm !
Yêu cầu công việc
WorkRequest
chứa danh sách các đối số cho worker, danh sách các đối số
các cặp chuỗi đại diện đại diện cho đầu vào mà worker có thể truy cập (đây không phải là
được thực thi nhưng bạn có thể dùng thông tin này để lưu vào bộ nhớ đệm) và mã yêu cầu là 0
đối với trình thực thi singleplex.
LƯU Ý: Mặc dù thông số kỹ thuật vùng đệm giao thức sử dụng "ví dụ về trường hợp rắn" (request_id
),
giao thức JSON sử dụng "kiểu lạc đà" (requestId
). Tài liệu này sử dụng quy ước viết hoa kiểu lạc đà
trong các ví dụ về JSON, nhưng viết hoa con rắn khi nói về trường đó bất kể
giao thức.
{
"arguments" : ["--some_argument"],
"inputs" : [
{ "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
{ "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
],
"requestId" : 12
}
Bạn có thể dùng trường verbosity
(không bắt buộc) để yêu cầu thêm dữ liệu đầu ra gỡ lỗi
khỏi trình thực thi. Điều này hoàn toàn tuỳ thuộc vào việc nhân viên xử lý nội dung gì và cách xuất dữ liệu ra sao. Cao hơn
các giá trị biểu thị kết quả chi tiết hơn. Chuyển cờ --worker_verbose
đến
Bazel đặt trường verbosity
thành 10, nhưng bạn có thể sử dụng giá trị nhỏ hơn hoặc lớn hơn
theo cách thủ công cho số lượng đầu ra khác nhau.
Chỉ những worker hỗ trợ trường sandbox_dir
(không bắt buộc) mới được sử dụng
hộp cát Multiplex.
Trả lời cho bài tập
WorkResponse
chứa mã yêu cầu, mã thoát bằng 0 hoặc khác 0 và
thông báo đầu ra mô tả mọi lỗi gặp phải trong quá trình xử lý hoặc thực thi
yêu cầu. Worker phải chụp stdout
và stderr
của bất kỳ công cụ nào
các cuộc gọi và báo cáo cuộc gọi đó thông qua WorkResponse
. Ghi vào stdout
của
quy trình của worker không an toàn vì sẽ ảnh hưởng đến giao thức của worker.
Việc ghi vào stderr
của quy trình worker là an toàn, nhưng kết quả là
được thu thập trong tệp nhật ký của mỗi nhân viên thay vì được ghi cho từng hành động.
{
"exitCode" : 1,
"output" : "Action failed with the following message:\nCould not find input
file \"/path/to/my/file/1\"",
"requestId" : 12
}
Theo tiêu chuẩn của protobuf, tất cả các trường là không bắt buộc. Tuy nhiên, Bazel yêu cầu
WorkRequest
và WorkResponse
tương ứng để có cùng yêu cầu
mã, vì vậy, bạn phải chỉ định mã yêu cầu nếu khác 0. Đây là một hồ sơ thanh toán hợp lệ
WorkResponse
.
{
"requestId" : 12,
}
request_id
là 0 biểu thị "singleplex" yêu cầu, được dùng khi yêu cầu này
không thể xử lý song song với các yêu cầu khác. Máy chủ đảm bảo rằng
một worker cụ thể nhận được các yêu cầu chỉ có request_id
0 hoặc chỉ
request_id
lớn hơn 0. Các yêu cầu singleplex được gửi nối tiếp, trong đó
Ví dụ: nếu máy chủ không gửi một yêu cầu khác cho đến khi nhận được
phản hồi (ngoại trừ các yêu cầu huỷ, xem bên dưới).
Lưu ý
- Mỗi vùng đệm giao thức bắt đầu bằng độ dài ở định dạng
varint
(xemMessageLite.writeDelimitedTo()
. - Yêu cầu và phản hồi JSON không được đặt sau chỉ báo kích thước.
- Yêu cầu JSON duy trì cấu trúc giống như protobuf, nhưng sử dụng cấu trúc tiêu chuẩn JSON và sử dụng quy ước viết hoa kiểu lạc đà (camel case) cho tất cả các tên trường.
- Để duy trì cùng các thuộc tính tương thích ngược và tiến dưới dạng protobuf, trình thực thi JSON phải chấp nhận các trường không xác định trong những thông báo này, và sử dụng giá trị mặc định của protobuf cho các giá trị còn thiếu.
- Bazel lưu trữ các yêu cầu dưới dạng protobuf và chuyển đổi chúng thành JSON bằng cách sử dụng định dạng JSON của protobuf
Huỷ
Worker có thể tuỳ ý cho phép huỷ các yêu cầu công việc trước khi hoàn tất.
Điều này đặc biệt hữu ích khi kết nối với quá trình thực thi động, trong đó
quá trình thực thi thường có thể bị gián đoạn bởi quá trình thực thi từ xa nhanh hơn. Để cho phép
huỷ, thêm supports-worker-cancellation: 1
vào
execution-requirements
(xem bên dưới) và đặt giá trị
Cờ --experimental_worker_cancellation
.
Yêu cầu huỷ là một WorkRequest
có nhóm trường cancel
(và
tương tự như phản hồi huỷ là WorkResponse
có was_cancelled
trường). Trường khác duy nhất phải có trong yêu cầu huỷ hoặc huỷ
phản hồi là request_id
, cho biết yêu cầu nào cần huỷ. request_id
trường sẽ là 0 đối với trình thực thi singleplex hoặc request_id
không phải là 0 của
đã gửi WorkRequest
cho multiplex worker. Máy chủ có thể gửi yêu cầu huỷ
đối với các yêu cầu mà worker đã phản hồi, trong trường hợp huỷ
phải được bỏ qua.
Mỗi tin nhắn WorkRequest
không huỷ phải được trả lời chính xác một lần, bất kể là
chứ không phải yêu cầu đó đã bị huỷ. Sau khi máy chủ gửi yêu cầu huỷ, worker có thể
phản hồi bằng WorkResponse
với tập hợp request_id
và was_cancelled
trường được đặt thành true. Việc gửi WorkResponse
thông thường cũng được chấp nhận, nhưng
Các trường output
và exit_code
sẽ bị bỏ qua.
Sau khi phản hồi được gửi cho WorkRequest
, worker không được chạm vào
trong thư mục đang hoạt động của nó. Máy chủ miễn phí dọn dẹp các tệp,
bao gồm cả các tệp tạm thời.
Tạo quy tắc sử dụng worker
Bạn cũng cần phải tạo một quy tắc tạo ra các hành động cần được thực hiện bởi . Việc lập ra một quy tắc Starlark sử dụng một worker cũng giống như tạo bất kỳ quy tắc nào khác.
Ngoài ra, quy tắc này cần phải chứa tệp tham chiếu đến chính worker đó và có một số yêu cầu đối với các hành động mà nó tạo ra.
Tham chiếu đến worker
Quy tắc sử dụng worker cần chứa trường tham chiếu đến worker
, nên bạn cần tạo một bản sao của quy tắc \*\_binary
để xác định
nhân viên của mình. Nếu worker của bạn được gọi là MyWorker.Java
, đây có thể là
quy tắc liên kết:
java_binary(
name = "worker",
srcs = ["MyWorker.Java"],
)
Điều này sẽ tạo ra "worker" nhãn này tham chiếu đến tệp nhị phân của worker. Sau đó, bạn sẽ xác định quy tắc sử dụng worker. Quy tắc này phải xác định thuộc tính tham chiếu đến tệp nhị phân của worker.
Nếu tệp nhị phân worker bạn tạo nằm trong gói có tên "work", gói này nằm ở trên cùng cấp bản dựng, đây có thể là định nghĩa thuộc tính:
"worker": attr.label(
default = Label("//work:worker"),
executable = True,
cfg = "exec",
)
cfg = "exec"
cho biết cần tạo worker để chạy trên
nền tảng thực thi thay vì trên nền tảng mục tiêu (ví dụ: worker được sử dụng
làm công cụ trong quá trình tạo bản dựng).
Yêu cầu đối với tác vụ công việc
Quy tắc sử dụng worker này tạo các thao tác để worker thực hiện. Các hành động có một số yêu cầu.
Trường "Arguments" (đối số). Thao tác này sẽ lấy danh sách các chuỗi, tất cả trừ chuỗi cuối cùng là các đối số được truyền đến worker khi khởi động. Phần tử cuối cùng trong các "đối số" danh sách là một đối số
flag-file
(@ có trước). Đã đọc trình thực thi các đối số từ tệp cờ được chỉ định trên cơ sở mỗi WorkRequest. Thông tin có thể ghi các đối số không khởi động cho worker vào tệp cờ này.Trường "execution-requirements", lấy từ điển có chứa
"supports-workers" : "1"
,"supports-multiplex-workers" : "1"
hoặc cả hai.Các "đối số" và "yêu cầu thực thi" là trường bắt buộc cho tất cả hành động gửi đến trình thực thi. Ngoài ra, các thao tác cần được thực thi bằng Trình thực thi JSON cần bao gồm
"requires-worker-protocol" : "json"
trong phần tử trường yêu cầu thực thi."requires-worker-protocol" : "proto"
cũng là một yêu cầu thực thi hợp lệ, mặc dù không bắt buộc đối với worker (trình thực thi proto), vì chúng là mặc định.Bạn cũng có thể thiết lập
worker-key-mnemonic
trong các yêu cầu thực thi. Chiến dịch này có thể hữu ích nếu bạn đang sử dụng lại tệp thực thi cho nhiều loại hành động và muốn phân biệt các hành động của worker này.Các tệp tạm thời được tạo trong quá trình thực hiện hành động đó nên được lưu vào thư mục của worker. Thao tác này sẽ bật chức năng hộp cát.
Giả sử định nghĩa quy tắc có từ "worker" được mô tả ở trên, ngoài việc
thành "srcs" thuộc tính biểu thị đầu vào, "đầu ra" phân bổ
biểu thị kết quả đầu ra và một "args" thuộc tính đại diện cho worker
đối số khởi động, lệnh gọi đến ctx.actions.run
có thể là:
ctx.actions.run(
inputs=ctx.files.srcs,
outputs=[ctx.outputs.output],
executable=ctx.executable.worker,
mnemonic="someMnemonic",
execution_requirements={
"supports-workers" : "1",
"requires-worker-protocol" : "json"},
arguments=ctx.attr.args + ["@flagfile"]
)
Để biết một ví dụ khác, hãy xem Triển khai trình thực thi liên tục.
Ví dụ
Cơ sở mã Bazel sử dụng trình biên dịch Java ngoài một ví dụ về trình thực thi JSON mà chúng tôi sử dụng trong thử nghiệm tích hợp.
Bạn có thể sử dụng giàn giáo để biến bất kỳ công cụ nào dựa trên Java thành một worker bằng cách truyền đúng lệnh gọi lại.
Để xem ví dụ về quy tắc sử dụng worker, hãy xem kiểm thử tích hợp nhân viên.
Các cộng tác viên bên ngoài đã triển khai worker bằng nhiều ngôn ngữ; lấy một xem Triển khai Polyglot cho trình thực thi cố định Bazel. Bạn có thể xem nhiều ví dụ khác trên GitHub!