การสร้างผู้ปฏิบัติงานถาวร

Worker แบบถาวร ช่วยให้บิลด์เร็วขึ้นได้ หากคุณมีการดำเนินการซ้ำๆ ในบิลด์ที่มีค่าใช้จ่ายในการเริ่มต้นสูงหรือจะได้รับประโยชน์จากการแคชข้ามการดำเนินการ คุณอาจต้องการใช้ Worker แบบถาวรของคุณเองเพื่อดำเนินการเหล่านี้

เซิร์ฟเวอร์ Bazel สื่อสารกับ Worker โดยใช้ stdin/stdout และรองรับการใช้บัฟเฟอร์โปรโตคอลหรือสตริง JSON

การใช้งาน Worker มี 2 ส่วน ดังนี้

การสร้าง Worker

Worker แบบถาวรต้องเป็นไปตามข้อกำหนดบางประการ ดังนี้

  • อ่าน WorkRequests จาก stdin
  • เขียน WorkResponses (และ WorkResponse เท่านั้น) ลงใน stdout
  • ยอมรับแฟล็ก --persistent_worker Wrapper ต้องจดจำแฟล็กบรรทัดคำสั่ง --persistent_worker และทำให้ตัวเองเป็นแบบถาวรก็ต่อเมื่อมีการส่งแฟล็กดังกล่าว ไม่เช่นนั้นจะต้องทำการคอมไพล์แบบครั้งเดียวแล้วออก

หากโปรแกรมของคุณเป็นไปตามข้อกำหนดเหล่านี้ คุณจะใช้โปรแกรมดังกล่าวเป็น Worker แบบถาวรได้

คำขอทำงาน

WorkRequest มีรายการอาร์กิวเมนต์สำหรับ Worker, รายการคู่เส้นทาง-ไดเจสต์ที่แสดงอินพุตที่ Worker เข้าถึงได้ (ระบบไม่ได้บังคับ แต่คุณใช้ข้อมูลนี้สำหรับการแคชได้) และรหัสคำขอ ซึ่งเป็น 0 สำหรับ Worker แบบซิงเกิลเพล็กซ์

หมายเหตุ: แม้ว่าข้อกำหนดบัฟเฟอร์โปรโตคอลจะใช้ "snake case" (request_id) แต่โปรโตคอล JSON จะใช้ "camel case" (requestId) เอกสารนี้ใช้ camel case ในตัวอย่าง JSON แต่ใช้ snake case เมื่อพูดถึงช่องโดยไม่คำนึงถึงโปรโตคอล

{
  "arguments" : ["--some_argument"],
  "inputs" : [
    { "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
    { "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
 ],
  "requestId" : 12
}

คุณใช้ช่อง verbosity ที่ไม่บังคับเพื่อขอเอาต์พุตการแก้ไขข้อบกพร่องเพิ่มเติมจาก Worker ได้ Worker จะเป็นผู้กำหนดเอาต์พุตและวิธีแสดงเอาต์พุตทั้งหมด ค่าที่สูงขึ้นจะแสดงเอาต์พุตที่ละเอียดมากขึ้น การส่งแฟล็ก --worker_verbose ไปยัง Bazel จะตั้งค่าช่อง verbosity เป็น 10 แต่คุณสามารถใช้ค่าที่น้อยกว่าหรือมากกว่าด้วยตนเองสำหรับเอาต์พุตจำนวนต่างๆ ได้

ช่อง sandbox_dir ที่ไม่บังคับใช้โดย Worker ที่รองรับ แซนด์บ็อกซ์แบบมัลติเพล็กซ์เท่านั้น

การตอบสนองต่อคำขอทำงาน

WorkResponse มีรหัสคำขอ รหัสออกเป็น 0 หรือไม่ใช่ 0 และสตริงเอาต์พุตที่อธิบายข้อผิดพลาดที่พบในการประมวลผลหรือการดำเนินการคำขอ ช่อง output มีคำอธิบายสั้นๆ และระบบอาจเขียนบันทึกแบบสมบูรณ์ลงใน stderr ของ Worker เนื่องจาก Worker เขียนได้เฉพาะ WorkResponses ลงใน stdout เท่านั้น Worker จึงมักจะเปลี่ยนเส้นทาง stdout ของเครื่องมือที่ใช้ไปยัง stderr

{
  "exitCode" : 1,
  "output" : "Action failed with the following message:\nCould not find input
    file \"/path/to/my/file/1\"",
  "requestId" : 12
}

ช่องทั้งหมดไม่บังคับตามมาตรฐานของ protobuf อย่างไรก็ตาม Bazel กำหนดให้ WorkRequest และ WorkResponse ที่เกี่ยวข้องต้องมีรหัสคำขอเดียวกัน ดังนั้นคุณต้องระบุรหัสคำขอหากไม่ใช่ 0 นี่คือ WorkResponse ที่ถูกต้อง

{
  "requestId" : 12,
}

request_id ที่มีค่าเป็น 0 แสดงถึงคำขอแบบ "ซิงเกิลเพล็กซ์" ซึ่งใช้เมื่อไม่สามารถประมวลผลคำขอพร้อมกับคำขออื่นๆ ได้ เซิร์ฟเวอร์รับประกันว่า Worker ที่กำหนดจะได้รับคำขอที่มี request_id เป็น 0 เท่านั้น หรือมี request_id มากกว่า 0 เท่านั้น ระบบจะส่งคำขอแบบซิงเกิลเพล็กซ์แบบอนุกรม เช่น เซิร์ฟเวอร์จะไม่ส่งคำขออื่นจนกว่าจะได้รับการตอบกลับ (ยกเว้นคำขอให้ยกเลิก ดูด้านล่าง)

หมายเหตุ

  • บัฟเฟอร์โปรโตคอลแต่ละรายการจะมีขนาดเป็นรูปแบบ varint นำหน้า (ดู MessageLite.writeDelimitedTo())
  • คำขอและการตอบกลับ JSON จะไม่มีตัวบ่งชี้ขนาดนำหน้า
  • คำขอ JSON มีโครงสร้างเหมือนกับ protobuf แต่ใช้ JSON มาตรฐานและใช้ camel case สำหรับชื่อช่องทั้งหมด
  • เพื่อให้คงคุณสมบัติความเข้ากันได้แบบย้อนกลับและไปข้างหน้าเหมือนกับ protobuf Worker JSON ต้องยอมรับช่องที่ไม่รู้จักในข้อความเหล่านี้ และใช้ค่าเริ่มต้นของ protobuf สำหรับค่าที่ขาดหายไป
  • Bazel จะจัดเก็บคำขอเป็น protobuf และแปลงเป็น JSON โดยใช้ รูปแบบ JSON ของ protobuf

การยกเลิก

Worker สามารถเลือกอนุญาตให้ยกเลิกคำขอทำงานก่อนที่จะเสร็จสิ้นได้ ซึ่งมีประโยชน์อย่างยิ่งในการดำเนินการแบบไดนามิก ซึ่งการดำเนินการในเครื่องอาจถูกขัดจังหวะเป็นประจำโดยการดำเนินการระยะไกลที่เร็วกว่า หากต้องการอนุญาตให้ยกเลิก ให้เพิ่ม supports-worker-cancellation: 1 ลงในช่อง execution-requirements (ดูด้านล่าง) และตั้งค่าแฟล็ก --experimental_worker_cancellation

**คำขอให้ยกเลิก** คือ WorkRequest ที่ตั้งค่าช่อง cancel (และในทำนองเดียวกัน **การตอบกลับให้ยกเลิก** คือ WorkResponse ที่ตั้งค่าช่อง was_cancelled) ช่องอื่นเพียงช่องเดียวที่ต้องอยู่ในคำขอให้ยกเลิกหรือการตอบกลับให้ยกเลิกคือ request_id ซึ่งระบุคำขอที่จะยกเลิก ช่อง request_id จะเป็น 0 สำหรับ Worker แบบซิงเกิลเพล็กซ์ หรือ request_id ที่ไม่ใช่ 0 ของ WorkRequest ที่ส่งก่อนหน้านี้สำหรับ Worker แบบมัลติเพล็กซ์ เซิร์ฟเวอร์อาจส่งคำขอให้ยกเลิกสำหรับคำขอที่ Worker ตอบกลับไปแล้ว ในกรณีนี้ต้องละเว้นคำขอให้ยกเลิก

ข้อความ WorkRequest ที่ไม่ใช่คำขอให้ยกเลิกแต่ละข้อความต้องได้รับการตอบกลับ 1 ครั้งเท่านั้น ไม่ว่าจะยกเลิกหรือไม่ก็ตาม เมื่อเซิร์ฟเวอร์ส่งคำขอให้ยกเลิกแล้ว Worker อาจตอบกลับด้วย WorkResponse ที่ตั้งค่า request_id และตั้งค่าช่อง was_cancelled เป็น "จริง" ระบบยังยอมรับการส่ง WorkResponse ปกติด้วย แต่จะละเว้นช่อง output และ exit_code

เมื่อส่งการตอบกลับสำหรับ WorkRequest แล้ว Worker จะต้องไม่แตะต้องไฟล์ในไดเรกทอรีการทำงาน เซิร์ฟเวอร์มีอิสระในการล้างไฟล์ รวมถึงไฟล์ชั่วคราว

การสร้างกฎที่ใช้ Worker

คุณจะต้องสร้างกฎที่สร้างการดำเนินการให้ Worker ดำเนินการด้วย การสร้างกฎ Starlark ที่ใช้ Worker ก็เหมือนกับการสร้างกฎอื่นๆ

นอกจากนี้ กฎยังต้องมีการอ้างอิงถึง Worker เอง และการดำเนินการที่กฎสร้างขึ้นต้องเป็นไปตามข้อกำหนดบางประการ

การอ้างอิงถึง Worker

กฎที่ใช้ Worker ต้องมีช่องที่อ้างอิงถึง Worker เอง ดังนั้นคุณจะต้องสร้างอินสแตนซ์ของกฎ \*\_binary เพื่อกำหนด Worker หาก Worker ชื่อ MyWorker.Java กฎที่เกี่ยวข้องอาจเป็นดังนี้

java_binary(
    name = "worker",
    srcs = ["MyWorker.Java"],
)

ซึ่งจะสร้างป้ายกำกับ "worker" ที่อ้างอิงถึงไบนารีของ Worker จากนั้นคุณจะกำหนดกฎที่ ใช้ Worker กฎนี้ควรกำหนดแอตทริบิวต์ที่อ้างอิงถึงไบนารีของ Worker

หากไบนารีของ Worker ที่คุณสร้างอยู่ในแพ็กเกจชื่อ "work" ซึ่งอยู่ที่ระดับบนสุดของบิลด์ คำจำกัดความแอตทริบิวต์อาจเป็นดังนี้

"worker": attr.label(
    default = Label("//work:worker"),
    executable = True,
    cfg = "exec",
)

cfg = "exec" แสดงว่าควรสร้าง Worker ให้ทำงานบนแพลตฟอร์มการดำเนินการของคุณแทนที่จะเป็นแพลตฟอร์มเป้าหมาย (เช่น ใช้ Worker เป็นเครื่องมือระหว่างบิลด์)

ข้อกำหนดการดำเนินการของ Worker

กฎที่ใช้ Worker จะสร้างการดำเนินการให้ Worker ดำเนินการ การดำเนินการเหล่านี้มีข้อกำหนด 2 ข้อ

  • ช่อง "arguments" ช่องนี้ใช้รายการสตริง ซึ่งทั้งหมดแต่ไม่ใช่รายการสุดท้ายเป็นอาร์กิวเมนต์ที่ส่งไปยัง Worker เมื่อเริ่มต้น องค์ประกอบสุดท้ายในรายการ "arguments" คืออาร์กิวเมนต์ flag-file (นำหน้าด้วย @) Worker จะอ่านอาร์กิวเมนต์จาก flagfile ที่ระบุตาม `WorkRequest` แต่ละรายการ กฎของคุณสามารถเขียนอาร์กิวเมนต์ที่ไม่ใช่การเริ่มต้นสำหรับ Worker ลงใน flagfile นี้ได้

  • ช่อง "execution-requirements" ซึ่งใช้พจนานุกรมที่มี "supports-workers" : "1", "supports-multiplex-workers" : "1", หรือทั้ง 2 อย่าง

    ช่อง "arguments" และ "execution-requirements" จำเป็นสำหรับการดำเนินการทั้งหมดที่ส่งไปยัง Worker นอกจากนี้ การดำเนินการที่ควรดำเนินการโดย Worker JSON ต้องมี "requires-worker-protocol" : "json" ใน ช่องข้อกำหนดการดำเนินการ "requires-worker-protocol" : "proto" ยังเป็น ข้อกำหนดการดำเนินการที่ถูกต้องด้วย แม้ว่าจะไม่จำเป็นสำหรับ Worker proto เนื่องจากเป็นค่าเริ่มต้น

    คุณยังตั้งค่า worker-key-mnemonic ในข้อกำหนดการดำเนินการได้ด้วย ซึ่งอาจมีประโยชน์หากคุณใช้ไฟล์ปฏิบัติการซ้ำสำหรับประเภทการดำเนินการหลายประเภทและต้องการแยกความแตกต่างของการดำเนินการตาม Worker นี้

  • ระบบควรบันทึกไฟล์ชั่วคราวที่สร้างขึ้นระหว่างการดำเนินการลงในไดเรกทอรีของ Worker ซึ่งจะช่วยให้แซนด์บ็อกซ์ทำงานได้

สมมติว่ามีการกำหนดกฎที่มีแอตทริบิวต์ "worker" ตามที่อธิบายไว้ข้างต้น นอกเหนือจากแอตทริบิวต์ "srcs" ที่แสดงอินพุต แอตทริบิวต์ "output" ที่แสดงเอาต์พุต และแอตทริบิวต์ "args" ที่แสดงอาร์กิวเมนต์การเริ่มต้นของ Worker การเรียก ctx.actions.run อาจเป็นดังนี้

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"]
 )

ดูอีกตัวอย่างได้ที่ การใช้ Worker แบบถาวร

ตัวอย่าง

ฐานของโค้ด Bazel ใช้ Worker คอมไพเลอร์ Java, นอกเหนือจาก Worker JSON ตัวอย่าง ที่ใช้ในการทดสอบการผสานรวม

คุณสามารถใช้ โครงสร้างพื้นฐาน ของ Worker เพื่อเปลี่ยนเครื่องมือที่ใช้ Java ให้เป็น Worker ได้โดยการส่งการเรียกกลับที่ถูกต้อง

ดูตัวอย่างกฎที่ใช้ Worker ได้ที่การทดสอบการผสานรวม Worker ของ Bazel

ผู้มีส่วนร่วมภายนอกได้ใช้ Worker ในภาษาต่างๆ มากมาย ดูได้ที่ การใช้งาน Worker แบบถาวรของ Bazel ในหลายภาษา คุณดูตัวอย่างอื่นๆ ได้อีกมากมายใน GitHub !