पर्सिस्टेंट वर्कर्स की मदद से, बिल्ड को तेज़ी से बनाया जा सकता है. अगर आपके बिल्ड में ऐसी कार्रवाइयां बार-बार की जाती हैं जिन्हें शुरू करने में ज़्यादा समय लगता है या जिन्हें क्रॉस-ऐक्शन कैशिंग से फ़ायदा मिल सकता है, तो इन कार्रवाइयों को करने के लिए, अपना पर्सिस्टेंट वर्कर लागू किया जा सकता है.
Bazel सर्वर, stdin/stdout का इस्तेमाल करके वर्कर से कम्यूनिकेट करता है. यह प्रोटोकॉल बफ़र या JSON स्ट्रिंग के इस्तेमाल की सुविधा देता है.
वर्कर को लागू करने के दो हिस्से होते हैं:
वर्कर बनाना
पर्सिस्टेंट वर्कर को इन ज़रूरी शर्तों को पूरा करना होता है:
- यह अपने
stdinसे WorkRequests पढ़ता है. - यह अपने
stdoutमें WorkResponses (और सिर्फ़WorkResponses) लिखता है. - यह
--persistent_workerफ़्लैग स्वीकार करता है. रैपर को--persistent_workerकमांड-लाइन फ़्लैग को पहचानना होगा. साथ ही, इसे सिर्फ़ तब पर्सिस्टेंट बनाना होगा, जब यह फ़्लैग पास किया गया हो. ऐसा न होने पर, इसे एक बार कंपाइल करके बंद करना होगा.
अगर आपका प्रोग्राम इन ज़रूरी शर्तों को पूरा करता है, तो इसे पर्सिस्टेंट वर्कर के तौर पर इस्तेमाल किया जा सकता है!
काम के अनुरोध
WorkRequest में वर्कर के लिए आर्ग्युमेंट की सूची, पाथ-डाइजेस्ट पेयर की सूची (इससे पता चलता है कि वर्कर किन इनपुट को ऐक्सेस कर सकता है. यह ज़रूरी नहीं है, लेकिन कैशिंग के लिए इस जानकारी का इस्तेमाल किया जा सकता है) और अनुरोध का आईडी होता है. सिंगलप्लेक्स वर्कर के लिए, अनुरोध का आईडी 0 होता है.
ध्यान दें: प्रोटोकॉल बफ़र की खास जानकारी में "स्नेक केस" (request_id) का इस्तेमाल किया जाता है, जबकि JSON प्रोटोकॉल में "कैमल केस" (requestId) का इस्तेमाल किया जाता है. इस दस्तावेज़ में, JSON के उदाहरणों में कैमल केस का इस्तेमाल किया गया है. हालांकि, प्रोटोकॉल के बावजूद, फ़ील्ड के बारे में बात करते समय स्नेक केस का इस्तेमाल किया गया है.
{
"arguments" : ["--some_argument"],
"inputs" : [
{ "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
{ "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
],
"requestId" : 12
}
वर्कर से ज़्यादा डीबग करने के लिए आउटपुट का अनुरोध करने के लिए, verbosity फ़ील्ड का इस्तेमाल किया जा सकता है. यह फ़ील्ड ज़रूरी नहीं है. वर्कर को क्या और कैसे आउटपुट करना है, यह पूरी तरह से वर्कर पर निर्भर करता है. ज़्यादा वैल्यू का मतलब है कि ज़्यादा वर्बोस आउटपुट मिलेगा. Bazel को --worker_verbose फ़्लैग पास करने पर, verbosity फ़ील्ड की वैल्यू 10 सेट हो जाती है. हालांकि, अलग-अलग आउटपुट के लिए, मैन्युअल तरीके से छोटी या बड़ी वैल्यू का इस्तेमाल किया जा सकता है.
ज़रूरी नहीं है कि sandbox_dir फ़ील्ड का इस्तेमाल सिर्फ़ उन वर्कर्स के लिए किया जाए जो
मल्टीप्लेक्स सैंडबॉक्सिंग की सुविधा देते हैं.
काम के जवाब
WorkResponse में अनुरोध का आईडी, ज़ीरो या नॉन-ज़ीरो एक्ज़िट कोड, और अनुरोध को प्रोसेस या एक्ज़िक्यूट करने में हुई किसी भी गड़बड़ी के बारे में बताने वाला आउटपुट मैसेज होता है. वर्कर को, कॉल किए गए किसी भी टूल के stdout और stderr को कैप्चर करना चाहिए. साथ ही, उन्हें WorkResponse के ज़रिए रिपोर्ट करना चाहिए. इसे वर्कर प्रोसेस के stdout में लिखना सुरक्षित नहीं है, क्योंकि इससे वर्कर प्रोटोकॉल में रुकावट आ सकती है.
इसे वर्कर प्रोसेस के stderr में लिखना सुरक्षित है. हालांकि, नतीजे को अलग-अलग कार्रवाइयों के बजाय, हर वर्कर के लिए लॉग फ़ाइल में इकट्ठा किया जाता है.
{
"exitCode" : 1,
"output" : "Action failed with the following message:\nCould not find input
file \"/path/to/my/file/1\"",
"requestId" : 12
}
प्रोटोबफ़ के लिए तय किए गए नियमों के मुताबिक, सभी फ़ील्ड ज़रूरी नहीं हैं. हालांकि, Bazel के लिए ज़रूरी है कि WorkRequest और उससे जुड़े WorkResponse का अनुरोध आईडी एक ही हो. इसलिए, अगर अनुरोध आईडी नॉन-ज़ीरो है, तो उसे तय करना ज़रूरी है. यह एक मान्य WorkResponse है.
{
"requestId" : 12,
}
0 का मतलब है "सिंगलप्लेक्स" अनुरोध. इसका इस्तेमाल तब किया जाता है, जब इस अनुरोध
को अन्य अनुरोधों के साथ-साथ प्रोसेस नहीं किया जा सकता.request_id सर्वर इस बात की गारंटी देता है कि किसी वर्कर को सिर्फ़ request_id 0 वाले अनुरोध या सिर्फ़ request_id की वैल्यू ज़ीरो से ज़्यादा वाले अनुरोध मिलेंगे. सिंगलप्लेक्स अनुरोध क्रम से भेजे जाते हैं. उदाहरण के लिए, अगर सर्वर को जवाब नहीं मिलता, तो वह दूसरा अनुरोध नहीं भेजता. हालांकि, रद्द करने के अनुरोध के मामले में ऐसा नहीं होता. इसके बारे में नीचे बताया गया है.
ज़रूरी जानकारी
- हर प्रोटोकॉल बफ़र से पहले, उसकी लंबाई
varintफ़ॉर्मैट में दी जाती है. इसके लिए,MessageLite.writeDelimitedTo()देखें. - JSON अनुरोधों और जवाबों से पहले, साइज़ इंडिकेटर नहीं दिया जाता.
- JSON अनुरोधों का स्ट्रक्चर, प्रोटोबफ़ के जैसा ही होता है. हालांकि, इनमें स्टैंडर्ड JSON का इस्तेमाल किया जाता है. साथ ही, सभी फ़ील्ड के नामों के लिए कैमल केस का इस्तेमाल किया जाता है.
- प्रोटोबफ़ की तरह, JSON वर्कर को भी बैकवर्ड और फ़ॉरवर्ड कंपैटिबिलिटी की प्रॉपर्टी बनाए रखनी होती हैं. इसलिए, JSON वर्कर को इन मैसेज में मौजूद ऐसे फ़ील्ड को अनदेखा करना होगा जिनके बारे में उसे जानकारी नहीं है. साथ ही, उसे उन वैल्यू के लिए प्रोटोबफ़ के डिफ़ॉल्ट का इस्तेमाल करना होगा जो मौजूद नहीं हैं.
- Bazel, अनुरोधों को प्रोटोबफ़ के तौर पर सेव करता है. साथ ही, प्रोटोबफ़ के JSON फ़ॉर्मैट का इस्तेमाल करके, उन्हें JSON में बदलता है
रद्द किया जाना
वर्कर के पास, काम के अनुरोधों को पूरा होने से पहले रद्द करने की अनुमति देने का विकल्प होता है.
यह सुविधा, डाइनैमिक एक्ज़ीक्यूशन के मामले में खास तौर पर काम आती है. इसमें, स्थानीय एक्ज़ीक्यूशन को, रिमोट एक्ज़ीक्यूशन की मदद से नियमित तौर पर रोका जा सकता है. रद्द करने की अनुमति देने के लिए, execution-requirements फ़ील्ड (नीचे देखें) में supports-worker-cancellation: 1 जोड़ें और --experimental_worker_cancellation फ़्लैग सेट करें.
एक रद्द करने का अनुरोध एक WorkRequest होता है, जिसमें cancel फ़ील्ड सेट होता है. इसी तरह,
एक रद्द करने का जवाब एक WorkResponse होता है, जिसमें was_cancelled
फ़ील्ड सेट होता है. रद्द करने के अनुरोध या रद्द करने के जवाब में, सिर्फ़ एक और फ़ील्ड होना चाहिए. यह फ़ील्ड request_id है. इससे पता चलता है कि किस अनुरोध को रद्द करना है. सिंगलप्लेक्स वर्कर के लिए, request_id फ़ील्ड की वैल्यू 0 होगी. वहीं, मल्टीप्लेक्स वर्कर के लिए, इसकी वैल्यू पहले भेजे गए WorkRequest के नॉन-ज़ीरो request_id के बराबर होगी. सर्वर, उन अनुरोधों के लिए रद्द करने के अनुरोध भेज सकता है जिनका जवाब वर्कर ने पहले ही दे दिया है. ऐसे में, रद्द करने के अनुरोध को अनदेखा करना होगा.
रद्द न किए गए हर WorkRequest मैसेज का जवाब सिर्फ़ एक बार दिया जाना चाहिए. भले ही, उसे रद्द कर दिया गया हो या नहीं. सर्वर के रद्द करने का अनुरोध भेजने के बाद, वर्कर request_id सेट करके और was_cancelled फ़ील्ड को 'सही' पर सेट करके, WorkResponse के साथ जवाब दे सकता है. सामान्य WorkResponse भेजना भी स्वीकार किया जाता है. हालांकि, output और exit_code फ़ील्ड को अनदेखा कर दिया जाएगा.
WorkRequest के लिए जवाब भेजने के बाद, वर्कर को अपनी वर्किंग डायरेक्ट्री में मौजूद फ़ाइलों में कोई बदलाव नहीं करना चाहिए. सर्वर के पास, अस्थायी फ़ाइलों सहित सभी फ़ाइलों को साफ़ करने की अनुमति होती है.
वर्कर का इस्तेमाल करने वाला नियम बनाना
आपको एक ऐसा नियम भी बनाना होगा जो वर्कर से की जाने वाली कार्रवाइयों को जनरेट करता है. वर्कर का इस्तेमाल करने वाला Starlark नियम बनाना, किसी अन्य नियम को बनाने जैसा ही है .
इसके अलावा, नियम में वर्कर का रेफ़रंस होना चाहिए. साथ ही, इससे जनरेट होने वाली कार्रवाइयों के लिए कुछ ज़रूरी शर्तें हैं.
वर्कर को रेफ़र करना
वर्कर का इस्तेमाल करने वाले नियम में, एक ऐसा फ़ील्ड होना चाहिए जो वर्कर को रेफ़र करता हो. इसलिए, आपको अपने वर्कर को तय करने के लिए, \*\_binary नियम का एक इंस्टेंस बनाना होगा. अगर आपके वर्कर का नाम MyWorker.Java है, तो इससे जुड़ा नियम यह हो सकता है:
java_binary(
name = "worker",
srcs = ["MyWorker.Java"],
)
इससे "worker" लेबल बनता है, जो वर्कर बाइनरी को रेफ़र करता है. इसके बाद, एक ऐसा नियम तय किया जाएगा जो वर्कर का इस्तेमाल करता है. इस नियम में, एक ऐसा एट्रिब्यूट तय किया जाना चाहिए जो वर्कर बाइनरी को रेफ़र करता हो.
अगर आपके बनाए गए वर्कर बाइनरी, "work" नाम के पैकेज में है और यह बिल्ड के टॉप लेवल पर है, तो एट्रिब्यूट की परिभाषा यह हो सकती है:
"worker": attr.label(
default = Label("//work:worker"),
executable = True,
cfg = "exec",
)
cfg = "exec" से पता चलता है कि वर्कर को टारगेट प्लैटफ़ॉर्म के बजाय, आपके
एक्ज़ीक्यूशन प्लैटफ़ॉर्म पर चलाने के लिए बनाया जाना चाहिए. इसका मतलब है कि बिल्ड के दौरान, वर्कर का इस्तेमाल टूल के तौर पर किया जाता है.
काम की कार्रवाई से जुड़ी ज़रूरी शर्तें
वर्कर का इस्तेमाल करने वाला नियम, वर्कर से की जाने वाली कार्रवाइयों को बनाता है. इन कार्रवाइयों के लिए कुछ ज़रूरी शर्तें हैं.
The "arguments" फ़ील्ड. इसमें स्ट्रिंग की सूची होती है. इनमें से आखिरी स्ट्रिंग को छोड़कर बाकी सभी स्ट्रिंग, स्टार्टअप के दौरान वर्कर को पास किए जाने वाले आर्ग्युमेंट होती हैं. "arguments" सूची में मौजूद आखिरी एलिमेंट,
flag-file(@-preceded) आर्ग्युमेंट होता है. वर्कर्स, हर WorkRequest के आधार पर, तय की गई फ़्लैगफ़ाइल से आर्ग्युमेंट पढ़ते हैं. आपका नियम, वर्कर के लिए नॉन-स्टार्टअप आर्ग्युमेंट को इस फ़्लैगफ़ाइल में लिख सकता है.The "execution-requirements" फ़ील्ड. इसमें
"supports-workers" : "1","supports-multiplex-workers" : "1", या दोनों को शामिल करने वाली डिक्शनरी होती है."arguments" और "execution-requirements" फ़ील्ड, वर्कर्स को भेजे जाने वाली सभी कार्रवाइयों के लिए ज़रूरी हैं. इसके अलावा, JSON वर्कर्स से की जाने वाली कार्रवाइयों के लिए, एक्ज़ीक्यूशन की ज़रूरी शर्तों वाले फ़ील्ड में
"requires-worker-protocol" : "json"शामिल करना ज़रूरी है."requires-worker-protocol" : "proto"भी एक मान्य एक्ज़ीक्यूशन की ज़रूरी शर्त है. हालांकि, यह प्रोटो वर्कर्स के लिए ज़रूरी नहीं है, क्योंकि वे डिफ़ॉल्ट रूप से प्रोटो वर्कर्स होते हैं.आपके पास, एक्ज़ीक्यूशन की ज़रूरी शर्तों में
worker-key-mnemonicसेट करने का विकल्प भी होता है. अगर एक से ज़्यादा तरह की कार्रवाइयों के लिए, एक्ज़ीक्यूटेबल का फिर से इस्तेमाल किया जा रहा है और इस वर्कर की मदद से कार्रवाइयों को अलग-अलग करना है, तो यह तरीका काम आ सकता है.कार्रवाई के दौरान जनरेट होने वाली अस्थायी फ़ाइलों को, वर्कर की डायरेक्ट्री में सेव किया जाना चाहिए. इससे सैंडबॉक्सिंग की सुविधा मिलती है.
ऊपर बताए गए "worker" एट्रिब्यूट के साथ-साथ, इनपुट को दिखाने वाले "srcs" एट्रिब्यूट, आउटपुट को दिखाने वाले "output" एट्रिब्यूट, और वर्कर स्टार्टअप आर्ग्युमेंट को दिखाने वाले "args" एट्रिब्यूट के साथ, नियम की परिभाषा को मानते हुए, 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"]
)
एक और उदाहरण के लिए, पर्सिस्टेंट वर्कर्स लागू करना देखें.
उदाहरण
Bazel के कोड बेस में, Java कंपाइलर वर्कर्स का इस्तेमाल किया जाता है. इसके अलावा, JSON वर्कर का एक उदाहरण भी दिया गया है. इसका इस्तेमाल हमारे इंटिग्रेशन टेस्ट में किया जाता है.
सही कॉलबैक पास करके, Java पर आधारित किसी भी टूल को वर्कर बनाने के लिए, उनके स्केफ़ोल्डिंग का इस्तेमाल किया जा सकता है.
वर्कर का इस्तेमाल करने वाले नियम का उदाहरण देखने के लिए, Bazel के वर्कर इंटिग्रेशन टेस्ट को देखें.
बाहरी योगदानकर्ताओं ने अलग-अलग भाषाओं में वर्कर्स लागू किए हैं. इसके लिए, Bazel के पर्सिस्टेंट वर्कर्स के पॉलीग्लॉट लागू करने के तरीके देखें. GitHub पर और भी कई उदाहरण देखे जा सकते हैं!