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.
Quá trình triển khai worker có hai phần:
Tạo dựng nhân viên
Một worker ổn định đáp ứng được một số yêu cầu:
- Phương thức này đọc 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 tuân thủ các yêu cầu này, thì chương trình đó có thể được dùng làm worker liên tục!
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 "cách viết hoa 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ể sử dụng trường verbosity
không bắt buộc để yêu cầu thêm đầu ra gỡ lỗi từ worker. Đ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ị cho biết 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 các lượng đầu ra khác nhau.
Trường sandbox_dir
không bắt buộc chỉ được các worker hỗ trợ hộp cát đa kênh sử dụng.
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à
chuỗi đầ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. Trường output
chứa nội dung mô tả ngắn; nhật ký đầy đủ có thể được ghi vào stderr
của worker. Vì worker chỉ có thể viết
WorkResponses
đến stdout
, trình chạy này thường chuyển hướng stdout
bất kỳ công cụ nào mà nó dùng để stderr
.
{
"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 WorkResponse
hợp lệ.
{
"requestId" : 12,
}
request_id
bằng 0 cho biết yêu cầu "singleplex", được dùng khi không thể xử lý yêu cầu này song song với các yêu cầu khác. Máy chủ đảm bảo rằng một worker nhất định sẽ nhận được các yêu cầu chỉ có request_id
0 hoặc chỉ có request_id
lớn hơn 0. Các yêu cầu Singleplex được gửi tuần tự, ví dụ: nếu máy chủ không gửi 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ỷ, hãy 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ó chỉ báo kích thước ở phía trướ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 liên kết với quá trình thực thi động, trong đó quá trình thực thi cục bộ thường xuyên bị gián đoạn bởi quá trình thực thi từ xa nhanh hơn. Để cho phép huỷ, hãy thêm supports-worker-cancellation: 1
vào trường execution-requirements
(xem bên dưới) và đặt cờ --experimental_worker_cancellation
.
Yêu cầu huỷ là WorkRequest
có trường cancel
được đặt (và tương tự, phản hồi huỷ là WorkResponse
có trường was_cancelled
được đặt). 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ỷ. Trường request_id
sẽ là 0 đối với worker đơn hoặc request_id
khác 0 của WorkRequest
đã gửi trước đó đối với worker đa năng. 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 request_id
được đặt và trường was_cancelled
được đặt thành true. Bạn cũng có thể gửi WorkResponse
thông thường, nhưng các trường output
và exit_code
sẽ bị bỏ qua.
Sau khi gửi phản hồi cho WorkRequest
, worker không được chạm vào các tệp trong thư mục đang hoạt động. 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 tạo quy tắc Starlark sử dụng 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 một trường tham chiếu đến chính worker đó, vì vậy, bạn cần tạo một thực thể của quy tắc \*\_binary
để xác định worker. Nếu worker của bạn được gọi là MyWorker.Java
, thì đây có thể là quy tắc liên kết:
java_binary(
name = "worker",
srcs = ["MyWorker.Java"],
)
Thao tác này sẽ tạo nhãn "worker" (worker) tham chiếu đến tệp nhị phân worker. Sau đó, bạn sẽ xác định một 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 rằng worker phải được tạo để chạy trên nền tảng thực thi thay vì trên nền tảng mục tiêu (tức là worker được 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 sẽ tạo các hành động để worker thực hiện. Các thao tác này có một vài yêu cầu.
Trường "Arguments" (đối số). Phương thức này lấy một 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 Worker 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 mà trình chạy JSON sẽ thực thi cần bao gồm
"requires-worker-protocol" : "json"
trong 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ể đặt
worker-key-mnemonic
trong các yêu cầu thực thi. Điều 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 thao tác và muốn phân biệt các thao tác theo 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ó thuộc tính "worker" được mô tả ở trên, ngoài thuộc tính "srcs" đại diện cho dữ liệu đầu vào, thuộc tính "output" đại diện cho dữ liệu đầu ra và thuộc tính "args" đại diện cho args khởi động worker, 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 ví dụ khác, hãy xem phần Triển khai worker ổn định.
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 khung công cụ của họ để 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 vào đú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 nhìn vào Triển khai Polyglot cho trình thực thi cố định Bazel. Bạn có thể tìm thấy nhiều ví dụ khác trên GitHub!