Tạo nhân viên cố định

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo sự cố Xem nguồn

Trình chạy ổn định có thể giúp bạn tạo bản dựng nhanh hơn. Nếu bạn có các hành động lặp lại trong bản dựng có chi phí khởi động cao hoặc sẽ được hưởng lợi từ việc lưu vào bộ nhớ đệm của nhiều hành động, bạn có thể muốn triển khai trình chạy cố định của riêng mình để 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. Máy chủ này hỗ trợ việc 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 trình chạy

Trình chạy liên tục tuân thủ một số yêu cầu sau:

  • Ứng dụng đọc WorkRequest từ stdin.
  • Tính năng này ghi WorkResponses (và chỉ WorkResponse) vào stdout.
  • Phương thức này chấp nhận cờ --persistent_worker. Trình bao bọc phải nhận ra cờ dòng lệnh --persistent_worker và chỉ khiến nó ổn định nếu cờ đó được truyền, nếu không, trình thu thập này phải thực hiện việc biên dịch và thoát một lần.

Nếu chương trình của bạn đáp ứng các yêu cầu này, thì chương trình có thể được dùng như một trình làm việc cố định!

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 cặp thông báo đường dẫn đại diện cho dữ liệu đầu vào mà worker có thể truy cập (không bắt buộc, nhưng bạn có thể sử dụng thông tin này để lưu vào bộ nhớ đệm) và mã yêu cầu (là 0 cho các worker chạy đơn).

LƯU Ý: Mặc dù thông số kỹ thuật của vùng đệm giao thức sử dụng "trường hợp rắn" (request_id), nhưng giao thức JSON sử dụng "trường hợp lạc đà" (requestId). Tài liệu này sử dụng kiểu viết hoa Camel trong các ví dụ về JSON, nhưng trường hợp rắn khi nói về trường này bất kể giao thức là gì.

{
  "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 đầu ra gỡ lỗi bổ sung từ worker. Điều này là hoàn toàn tùy thuộc vào những gì và cách xuất đầu ra của nhân viên. Giá trị cao hơn cho biết kết quả chi tiết hơn. Việc truyền cờ --worker_verbose vào Bazel sẽ đặt trường verbosity thành 10, nhưng có thể sử dụng các giá trị nhỏ hơn hoặc lớn hơn theo cách thủ công cho nhiều mức đầu ra.

Trường tùy chọn sandbox_dir chỉ được các trình chạy hỗ trợ Multiplex sandboxing sử dụng.

Câu trả lời công việc

WorkResponse chứa mã yêu cầu, mã thoát bằng 0 hoặ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 một đoạn mô tả ngắn; các nhật ký hoàn chỉnh có thể được ghi vào stderr của worker. Vì trình chạy chỉ có thể ghi WorkResponses vào stdout, nên trình chạy này thường chuyển hướng stdout của bất kỳ công cụ nào mà trình này sử dụng sang 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 protobufs, tất cả các trường là không bắt buộc. Tuy nhiên, Bazel yêu cầu WorkRequestWorkResponse tương ứng phải có cùng mã yêu cầu, vì vậy mã yêu cầu phải được chỉ định nếu khác 0. Đây là WorkResponse hợp lệ.

{
  "requestId" : 12,
}

request_id bằng 0 cho biết yêu cầu "singleplex", được sử 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 nhận 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, 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ỷ, hãy xem nội dung bên dưới).

Ghi chú

  • Mỗi vùng đệm giao thức đứng sau độ dài của định dạng varint (xem MessageLite.writeDelimitedTo()).
  • Yêu cầu và phản hồi JSON không đứng sau một chỉ báo kích thước.
  • Các yêu cầu JSON giữ nguyên cấu trúc giống như protobuf nhưng sử dụng JSON chuẩn và sử dụng kiểu viết hoa camel cho tất cả tên trường.
  • Để duy trì các thuộc tính tương thích ngược và tiến lên giống như protobuf, các trình chạy JSON phải chấp nhận các trường không xác định trong các thông báo này, đồng thời sử dụng các giá trị mặc định của protobuf cho các giá trị bị thiếu.
  • Bazel lưu trữ các yêu cầu dưới dạng protobufs và chuyển đổi chúng thành JSON bằ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 cần thực thi động, trong đó quá trình thực thi cục bộ có thể 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à một WorkRequest có đặt trường cancel (và tương tự như vậy, phản hồi huỷ là một WorkResponse có nhóm trường was_cancelled. Trường duy nhất khác phải nằm trong yêu cầu huỷ hoặc phản hồi huỷ 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 các worker chạy singleplex hoặc trường request_id không phải là 0 trong WorkRequest đã gửi trước đó đối với các multiplexWorker. Máy chủ có thể gửi yêu cầu huỷ cho những yêu cầu mà worker đã phản hồi. Trong trường hợp đó, yêu cầu huỷ phải bị bỏ qua.

Mỗi thông báo WorkRequest không bị huỷ phải được trả lời chính xác một lần cho dù tin nhắn đó có bị huỷ hay không. Sau khi máy chủ gửi yêu cầu huỷ, worker này có thể phản hồi bằng một WorkResponse được đặt request_id và trường was_cancelled được đặt thành true. Việc gửi một WorkResponse thông thường cũng được chấp nhận, nhưng các trường outputexit_code sẽ bị bỏ qua.

Sau khi phản hồi đã được gửi cho WorkRequest, worker này không được chạm vào các tệp trong thư mục đang hoạt động. Máy chủ có thể 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 này

Bạn cũng sẽ cần tạo một quy tắc tạo ra các hành động để trình chạy thực hiện. Việc tạo quy tắc Starlark sử dụng worker giống như khi tạo bất kỳ quy tắc nào khác.

Ngoài ra, quy tắc cần chứa tệp tham chiếu đến chính trình chạy này và có một số yêu cầu đối với các thao tác mà quy tắc tạo ra.

Tham chiếu đến trình chạy

Quy tắc sử dụng worker đó phải chứa một trường tham chiếu đến chính trình chạy đó, vì vậy bạn cần tạo một bản sao của quy tắc \*\_binary để xác định trình chạy của mình. Nếu worker 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" (trình chạy), đề cập đế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 một 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 một gói có tên là "work", ở cấp cao nhất của 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 này cần được xây dựng để 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à trình chạy này được dùng làm công cụ trong quá trình tạo bản dựng).

Các yêu cầu về hành động đối với công việc

Quy tắc sử dụng trình chạy này sẽ tạo hành động để trình chạy thực hiện. Những hành động này có một số yêu cầu.

  • Trường "argument" (đối số). Thao tác này sẽ lấy danh sách các chuỗi, tất cả các chuỗi, ngoại trừ 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 danh sách "argument" (đối số) là một đối số flag-file (@-preceged). Trình chạy đọc các đối số từ tệp cờ được chỉ định trên cơ sở từng WorkRequest. Quy tắc của bạn có thể ghi các đối số không khởi động cho worker này vào tệp cờ này.

  • Trường "execution-requirements" (yêu cầu thực thi), lấy từ điển chứa "supports-workers" : "1", "supports-multiplex-workers" : "1" hoặc cả hai.

    Các trường "đối số" và "yêu cầu thực thi" là bắt buộc đối với tất cả các thao tác gửi đến worker. Ngoài ra, các hành động mà trình chạy JSON thực thi cần phải 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 các worker proto, vì các tuỳ chọn này 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 của trình chạy này.

  • Các tệp tạm thời được tạo trong quá trình thực hiện hành động sẽ được lưu vào thư mục của trình chạy. Cấu hình này bật hộp cát.

Giả sử định nghĩa quy tắc có thuộc tính "worker" được mô tả ở trên, thì ngoài thuộc tính "srcs" biểu thị đầu vào, thuộc tính "output" đại diện cho kết quả và thuộc tính "args" đại diện cho tham số khởi động của 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"]
 )

Để xem ví dụ khác, hãy xem phần Triển khai worker liên tục.

Ví dụ

Cơ sở mã Bazel sử dụng Worker của trình biên dịch Java, ngoài trình chạy JSON mẫu được sử dụng trong các kiểm thử tích hợp của chúng tôi.

Bạn có thể sử dụng tính năng nâng cao (scaffolding) của họ để biến mọi công cụ dựa trên Java thành một worker bằng cách truyền lệnh gọi lại chính xác.

Để xem ví dụ về quy tắc sử dụng worker, hãy xem thử nghiệm tích hợp trình chạy của Bazel.

Các cộng tác viên bên ngoài đã triển khai trình chạy bằng nhiều ngôn ngữ; hãy xem qua phần triển khai Polylyglot của các trình chạy cố định Bazel. Bạn có thể tìm thêm nhiều ví dụ khác trên GitHub!