Skyframe StateMachines का इस्तेमाल करने के लिए गाइड

समस्या की शिकायत करें सोर्स देखें

खास जानकारी

Skyframe StateMachine एक डिकंस्ट्रक्ट फ़ंक्शन-ऑब्जेक्ट है, जो हीप पर मौजूद होता है. जब ज़रूरी वैल्यू तुरंत उपलब्ध नहीं होती, लेकिन एसिंक्रोनस तरीके से कंप्यूट की जाती हैं, तो यह सुविधा सुविधाजनक और बिना किसी अतिरिक्त शुल्क के आकलन1 की सुविधा देती है. इंतज़ार करने के दौरान, StateMachine थ्रेड रिसॉर्स को बांध नहीं सकता. इसके बजाय, इसे निलंबित करके फिर से शुरू करना पड़ता है. इस तरह डीकंस्ट्रक्शन, री-एंट्री पॉइंट को साफ़ तौर पर दिखाता है, ताकि पहले से कंप्यूटेशन छोड़े जा सकें.

StateMachine का इस्तेमाल क्रमों, ब्रांचिंग, और स्ट्रक्चर्ड लॉजिकल कंकरंसी को दिखाने के लिए किया जा सकता है. इन्हें खास तौर पर, Skyframe इंटरैक्शन के लिए तैयार किया जाता है. StateMachines को बड़े StateMachines में बनाया जा सकता है और सब-StateMachine को शेयर किया जा सकता है. कॉनकरेंसी हमेशा हैरारकी के हिसाब से बनाई जाती है और यह पूरी तरह लॉजिकल होता है. एक साथ काम करने वाला हर सबटास्क, शेयर किए गए एक पैरंट Skyफ़ंक्शन थ्रेड में चलता है.

शुरुआती जानकारी

यह सेक्शन, java.com.google.devtools.build.skyframe.state पैकेज में मौजूद StateMachine के बारे में संक्षेप में जानकारी देता है और इसके बारे में बताता है.

Skyframe के रीस्टार्ट होने के बारे में कम शब्दों में जानकारी

Skyframe एक फ़्रेमवर्क है, जो डिपेंडेंसी ग्राफ़ का पैरलल इवैलुएशन करता है. ग्राफ़ का हर नोड Skyफ़ंक्शन के मूल्यांकन से मेल खाता है. इसमें SkyKey उसके पैरामीटर और SkyValue के पैरामीटर के बारे में बताती है. कंप्यूटेशनल मॉडल इस तरह का है कि Skyफ़ंक्शन, SkyKey के ज़रिए SkyValues को खोज सकता है. इससे दूसरे Skyफ़ंक्शन का साथ-साथ बार-बार मूल्यांकन किया जाता है. ब्लॉक करने के बजाय, जब अनुरोध किया गया SkyValue अब तक तैयार नहीं होता है, क्योंकि कंप्यूटेशन का कुछ सबग्राफ़ पूरा नहीं होने की वजह से, उसे ब्लॉक करने के बजाय वह null getValue रिस्पॉन्स के तौर पर जवाब देता है. इस रिस्पॉन्स के तौर पर SkyValue के बजाय null दिखाएं. इससे पता चलता है कि इनपुट उपलब्ध न होने की वजह से वह अधूरा है. जब पहले अनुरोध किए गए सभी SkyValues उपलब्ध हो जाते हैं, तो SkyFrames फिर से शुरू करता है.

SkyKeyComputeState के शुरू होने से पहले, रीस्टार्ट को हैंडल करने का पारंपरिक तरीका था कंप्यूटेशन को फिर से पूरी तरह से चलाना. हालांकि, इसमें क्वाड्रेटिक कॉम्प्लेक्सिटी है, लेकिन इस तरह से लिखा गया फ़ंक्शन आखिरकार पूरा हो जाता है, क्योंकि हर बार फिर से चलाया जाता है, लेकिन कम लुकअप null देते हैं. SkyKeyComputeState का इस्तेमाल करके, हाथ से तय किए गए चेक-पॉइंट डेटा को Skyफ़ंक्शन के साथ जोड़ा जा सकता है. इससे एक अहम फिर से कंप्यूटेशन (कैलकुलेशन) को बचाया जा सकता है.

StateMachine ऐसे ऑब्जेक्ट होते हैं जो SkyKeyComputeState में रहते हैं. साथ ही, जब Skyफ़ंक्शन रीस्टार्ट होता है, तो करीब-करीब सभी रीकंप्यूटेशन हटा दिए जाते हैं (यह मानते हुए कि SkyKeyComputeState कैश मेमोरी में सेव नहीं होता). इसके लिए, सस्पेंड और एक्ज़ीक्यूशन हुक को फिर से चालू किया जा सकता है.

SkyKeyComputeState में स्टेटफ़ुल कंप्यूटेशन

ऑब्जेक्ट-ओरिएंटेड डिज़ाइन के हिसाब से, डेटा की पूरी वैल्यू के बजाय SkyKeyComputeState में कंप्यूटेशनल ऑब्जेक्ट को सेव करना सही रहता है. Java में, ऑब्जेक्ट को ले जाने वाले व्यवहार की कम से कम जानकारी एक फ़ंक्शनल इंटरफ़ेस है और यही काफ़ी है. StateMachine में, ऐसी परिभाषा है जो बार-बार मिलती है2.

@FunctionalInterface
public interface StateMachine {
  StateMachine step(Tasks tasks) throws InterruptedException;
}

Tasks इंटरफ़ेस, SkyFunction.Environment जैसा है. हालांकि, इसे एसिंक्रोनस तरीके से डिज़ाइन किया गया है. साथ ही, यह लॉजिकल तरीके से एक साथ किए जाने वाले सबटास्क की सुविधा देता है3.

step की रिटर्न वैल्यू एक और StateMachine है, जो सीधे तौर पर, चरणों के क्रम की स्पेसिफ़िकेशन की अनुमति देता है. StateMachine के पूरा होने पर, step DONE दिखाता है. उदाहरण के लिए:

class HelloWorld implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    System.out.println("hello");
    return this::step2;  // The next step is HelloWorld.step2.
  }

  private StateMachine step2(Tasks tasks) {
     System.out.println("world");
     // DONE is special value defined in the `StateMachine` interface signaling
     // that the computation is done.
     return DONE;
  }
}

नीचे दिए गए आउटपुट के साथ StateMachine की जानकारी देता है.

hello
world

ध्यान दें कि this::step2 तरीके का रेफ़रंस, StateMachine भी है, क्योंकि step2 StateMachine के काम करने वाले इंटरफ़ेस की परिभाषा से संतुष्ट होते हैं. तरीके रेफ़रंस, StateMachine में अगली स्थिति बताने का सबसे आम तरीका है.

निलंबित और फिर से शुरू करना

आसानी से, कंप्यूटेशन को मोनोलिथिक फ़ंक्शन के बजाय StateMachine चरणों में तोड़ने से, कंप्यूटेशन को suspend करने और suspend के लिए ज़रूरी हुक मिलते हैं. StateMachine.step के वापस लौटने पर, साफ़ तौर पर निलंबन बिंदु साफ़ हो जाता है. दिखाई गई StateMachine वैल्यू से तय किए गए कंटिन्यूशन का मतलब साफ़ तौर पर फिर से शुरू करें पॉइंट है. इसलिए, गणना से बचा जा सकता है, क्योंकि कंप्यूटेशन को ठीक वहीं से शुरू किया जा सकता है जहां उसे छोड़ा गया था.

कॉलबैक, कंटिन्यूएशन, और एसिंक्रोनस कंप्यूटेशन

तकनीकी भाषा में कहें, तो StateMachine यह continuation के तौर पर काम करता है, जो यह तय करता है कि बाद में लागू होने वाला कैलकुलेशन लागू किया जाए या नहीं. ब्लॉक करने के बजाय, StateMachine step फ़ंक्शन से वापस आकर अपने हिसाब से suspend कर सकता है. यह कंट्रोल, Driver पर वापस ट्रांसफ़र कर देता है. इसके बाद, Driver किसी तैयार StateMachine पर स्विच कर सकता है या कंट्रोल को वापस Skyframe पर छोड़ सकता है.

परंपरागत तौर पर, callbacks और callbacks, एक ही सिद्धांत में पिरोए जाते हैं. हालांकि, StateMachine दोनों के बीच अंतर बना रहता है.

  • कॉलबैक - यह बताता है कि एसिंक्रोनस कंप्यूटेशन के नतीजों को कहां सेव करना है.
  • जारी है - यह फ़ंक्शन चलने की अगली स्थिति की जानकारी देता है.

एसिंक्रोनस ऑपरेशन को शुरू करते समय कॉलबैक की ज़रूरत होती है, जिसका मतलब है कि तरीका इस्तेमाल करने के तुरंत बाद असल कार्रवाई नहीं होती, जैसा कि SkyValue लुकअप के मामले में होता है. कॉलबैक को जितना हो सके उतना आसान रखें.

कन्टिन्यूएशंस, StateMachine की StateMachine रिटर्न वैल्यू होती हैं. ये सभी एसिंक्रोनस कंप्यूटेशन से समाधान होने के बाद, कॉम्प्लेक्स एक्ज़ीक्यूशन को इनकैप्चर करती हैं. इस व्यवस्थित तरीके से कॉलबैक की जटिलता को प्रबंधित करने में मदद मिलती है.

टास्क

Tasks इंटरफ़ेस, StateMachines के साथ एक एपीआई उपलब्ध कराता है. इसकी मदद से, SkyKey by का SkyValues खोजे जा सकते हैं. साथ ही, एक साथ किए जा रहे सबटास्क भी शेड्यूल किए जा सकते हैं.

interface Tasks {
  void enqueue(StateMachine subtask);

  void lookUp(SkyKey key, Consumer<SkyValue> sink);

  <E extends Exception>
  void lookUp(SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);

  // lookUp overloads for 2 and 3 exception types exist, but are elided here.
}

SkyValue लुकअप

SkyValues खोजने के लिए StateMachine, Tasks.lookUp ओवरलोड का इस्तेमाल करता है. ये SkyFunction.Environment.getValue और SkyFunction.Environment.getValueOrThrow के जैसे हैं. साथ ही, इनमें अपवाद के तौर पर एक जैसे सिमैंटिक हैं. लागू करने पर, लुकअप तुरंत नहीं होता. बल्कि, ऐसा करने से पहले बैच4 ज़्यादा से ज़्यादा लुकअप हो सकते हैं. शायद यह वैल्यू तुरंत उपलब्ध न हो. उदाहरण के लिए, Skyframe को रीस्टार्ट करना ज़रूरी हो. इसलिए, कॉलर यह बताता है कि नतीजे के तौर पर मिलने वाली वैल्यू पर क्या करना है. इसके लिए, कॉलबैक का इस्तेमाल करें.

StateMachine प्रोसेसर (Drivers और SkyFrame का ब्रिजिंग) गारंटी देता है कि अगला स्टेटस शुरू होने से पहले, वैल्यू उपलब्ध हो जाएगी. इसका एक उदाहरण नीचे दिया गया है.

class DoesLookup implements StateMachine, Consumer<SkyValue> {
  private Value value;

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.lookUp(new Key(), (Consumer<SkyValue>) this);
    return this::processValue;
  }

  // The `lookUp` call in `step` causes this to be called before `processValue`.
  @Override  // Implementation of Consumer<SkyValue>.
  public void accept(SkyValue value) {
    this.value = (Value)value;
  }

  private StateMachine processValue(Tasks tasks) {
    System.out.println(value);  // Prints the string representation of `value`.
    return DONE;
  }
}

ऊपर दिए गए उदाहरण में, पहला चरण new Key() को खोजता है और this को उपभोक्ता के तौर पर पास करता है. ऐसा इसलिए हो सकता है, क्योंकि DoesLookup Consumer<SkyValue> को लागू करता है.

समझौते के हिसाब से, अगली स्थिति DoesLookup.processValue के शुरू होने से पहले, DoesLookup.step के सभी लुकअप पूरे हो जाते हैं. इसलिए, processValue में ऐक्सेस करने पर value उपलब्ध होता है.

सबटास्क

Tasks.enqueue, लॉजिकल तरीके से एक साथ किए जाने वाले सबटास्क को पूरा करने का अनुरोध करता है. सबटास्क भी StateMachine हैं और वे सामान्य StateMachine से कुछ भी कर सकते हैं. इसमें बार-बार ज़्यादा सबटास्क बनाना या SkyValues देखना शामिल है. lookUp की तरह, स्टेट मशीन ड्राइवर यह पक्का करता है कि अगले चरण पर जाने से पहले सभी सबटास्क पूरे हों. इसका एक उदाहरण नीचे दिया गया है.

class Subtasks implements StateMachine {
  private int i = 0;

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.enqueue(new Subtask1());
    tasks.enqueue(new Subtask2());
    // The next step is Subtasks.processResults. It won't be called until both
    // Subtask1 and Subtask 2 are complete.
    return this::processResults;
  }

  private StateMachine processResults(Tasks tasks) {
    System.out.println(i);  // Prints "3".
    return DONE;  // Subtasks is done.
  }

  private class Subtask1 implements StateMachine {
    @Override
    public StateMachine step(Tasks tasks) {
      i += 1;
      return DONE;  // Subtask1 is done.
    }
  }

  private class Subtask2 implements StateMachine {
    @Override
    public StateMachine step(Tasks tasks) {
      i += 2;
      return DONE;  // Subtask2 is done.
    }
  }
}

Subtask1 और Subtask2 लॉजिकल तौर पर एक साथ काम करते हैं. हालांकि, सब कुछ एक ही थ्रेड में चलता है. इसलिए, i के "एक ही समय पर होने वाले" अपडेट को सिंक करने की ज़रूरत नहीं होती.

स्ट्रक्चर्ड एक साथ काम करने वाली कसरत

अगली स्थिति पर जाने से पहले, हर lookUp और enqueue का समाधान होना ज़रूरी है. इसका मतलब है कि एक साथ कई काम नहीं किए जा सकते. नीचे दिए गए उदाहरण के मुताबिक, हैरारकी5 को एक साथ रखा जा सकता है.

स्ट्रक्चर्ड कॉनकरेंसी

यूएमएल से यह कहना मुश्किल है कि एक साथ काम करने वाली संरचना से एक पेड़ बनता है. इसमें पेड़ों की संरचना को बेहतर तरीके से दिखाने के लिए, वैकल्पिक व्यू उपलब्ध हैं.

अनस्ट्रक्चर्ड कॉनकरेंसी

एक ही स्ट्रक्चर्ड से स्ट्रक्चर्ड डेटा सेट अप करना, उसके बारे में तर्क देना ज़्यादा आसान होता है.

कंपोज़िशन और कंट्रोल फ़्लो के पैटर्न

इस सेक्शन में उदाहरण देकर बताया गया है कि एक से ज़्यादा StateMachine कैसे बनाए जा सकते हैं. साथ ही, कंट्रोल फ़्लो से जुड़ी कुछ समस्याओं को हल करने का तरीका भी बताया गया है.

क्रम से लगाई गई स्थितियां

यह सबसे सामान्य और आसान कंट्रोल फ़्लो पैटर्न है. इसका उदाहरण SkyKeyComputeState के अंदर स्टेटसफ़ुल कंप्यूटेशन में दिखाया गया है.

ब्रांचिंग

StateMachine में ब्रांचिंग स्टेट पाने के लिए, सामान्य Java कंट्रोल फ़्लो का इस्तेमाल करके, अलग-अलग वैल्यू दिखाएं, जैसा कि इस उदाहरण में दिखाया गया है.

class Branch implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    // Returns different state machines, depending on condition.
    if (shouldUseA()) {
      return this::performA;
    }
    return this::performB;
  }
  …
}

कुछ ब्रांच आम तौर पर DONE को वापस करती हैं, ताकि शुरुआत से ही काम पूरा हो सके.

बेहतर क्रम में चलने वाला कंपोज़िशन

StateMachine कंट्रोल की संरचना, मेमोरी नहीं होती, इसलिए StateMachine परिभाषाओं को सबटास्क के तौर पर शेयर करना कभी-कभी मुश्किल हो सकता है. मान लें कि M1 और M2 StateMachine इंस्टेंस हैं, जिनमें StateMachine, S है, जहां M1 और M2 क्रम से <A, S, B> और <X, S, Y> हैं. समस्या यह है कि S को यह पता नहीं चलता कि पूरा होने के बाद B को जारी रखना है या Y पर. साथ ही, StateMachine काफ़ी कॉल स्टैक नहीं रखते. इस सेक्शन में, ऐसा करने की कुछ तकनीकों के बारे में बताया गया है.

टर्मिनल सीक्वेंस एलिमेंट के तौर पर StateMachine

इससे शुरुआती समस्या हल नहीं होती. यह सिर्फ़ तब दिखाता है, जब शेयर किया गया StateMachine, क्रम में टर्मिनल होता है.

// S is the shared state machine.
class S implements StateMachine { … }

class M1 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performA();
    return new S();
  }
}

class M2 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performX();
    return new S();
  }
}

यह तब भी काम करता है, जब S खुद एक जटिल स्टेट मशीन हो.

क्रम में कंपोज़िशन के लिए सबटास्क

हालांकि, इस बात की गारंटी है कि सूची में शामिल सबटास्क अगली स्थिति से पहले ही पूरे हो जाएंगे. इसलिए, कभी-कभी सबटास्क के तरीके का थोड़ा गलत इस्तेमाल6 किया जा सकता है.

class M1 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performA();
    // S starts after `step` returns and by contract must complete before `doB`
    // begins. It is effectively sequential, inducing the sequence < A, S, B >.
    tasks.enqueue(new S());
    return this::doB;
  }

  private StateMachine doB(Tasks tasks) {
    performB();
    return DONE;
  }
}

class M2 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performX();
    // Similarly, this induces the sequence < X, S, Y>.
    tasks.enqueue(new S());
    return this::doY;
  }

  private StateMachine doY(Tasks tasks) {
    performY();
    return DONE;
  }
}

runAfter इंजेक्शन

कभी-कभी, Tasks.enqueue का गलत इस्तेमाल करना मुमकिन नहीं होता है. ऐसा इसलिए होता है, क्योंकि ऐसे अन्य पैरलल सबटास्क या Tasks.lookUp कॉल होते हैं जिन्हें सेकने से पहले पूरा करना ज़रूरी होता है. इस मामले में, runAfter पैरामीटर को S में इंजेक्ट करके, S को यह बताया जा सकता है कि आगे क्या करना है.

class S implements StateMachine {
  // Specifies what to run after S completes.
  private final StateMachine runAfter;

  @Override
  public StateMachine step(Tasks tasks) {
    … // Performs some computations.
    return this::processResults;
  }

  @Nullable
  private StateMachine processResults(Tasks tasks) {
    … // Does some additional processing.

    // Executes the state machine defined by `runAfter` after S completes.
    return runAfter;
  }
}

class M1 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performA();
    // Passes `this::doB` as the `runAfter` parameter of S, resulting in the
    // sequence < A, S, B >.
    return new S(/* runAfter= */ this::doB);
  }

  private StateMachine doB(Tasks tasks) {
    performB();
    return DONE;
  }
}

class M2 implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks) {
    performX();
    // Passes `this::doY` as the `runAfter` parameter of S, resulting in the
    // sequence < X, S, Y >.
    return new S(/* runAfter= */ this::doY);
  }

  private StateMachine doY(Tasks tasks) {
    performY();
    return DONE;
  }
}

यह तरीका सबटास्क में बुरा बर्ताव करने से ज़्यादा आसान है. हालांकि, इसे बिना किसी परेशानी के लागू किया जा सकता है. उदाहरण के लिए, runAfter के साथ कई StateMachine को नेस्ट करके, कॉलबैक हेल को हासिल किया जा सकता है. इसके बजाय, क्रम में लगने वाले runAfter को सामान्य क्रम में रखें.

  return new S(/* runAfter= */ new T(/* runAfter= */ this::nextStep))

को इनके साथ बदला जा सकता है.

  private StateMachine step1(Tasks tasks) {
     doStep1();
     return new S(/* runAfter= */ this::intermediateStep);
  }

  private StateMachine intermediateStep(Tasks tasks) {
    return new T(/* runAfter= */ this::nextStep);
  }

अनुमति नहीं है विकल्प: runAfterUnlessError

पिछले एक ड्राफ़्ट में, हमने ऐसे runAfterUnlessError पर विचार किया था जो गड़बड़ियों से पहले ही रद्द हो जाएगा. यह इस तथ्य से प्रेरित था कि गड़बड़ियों की जांच अक्सर दो बार होती है. एक बार इसकी जांच StateMachine से होती है, जिसका रेफ़रंस runAfter होता है और एक बार runAfter मशीन से.

सोच-समझकर फ़ैसला लेने के बाद, हमने फ़ैसला लिया है कि गड़बड़ियों की जांच करने के बजाय, कोड को एक जैसा रखना ज़्यादा ज़रूरी है. अगर runAfter तरीका, tasks.enqueue तरीके के साथ ठीक तरह से काम नहीं करता, जिसमें गड़बड़ी की जांच करना ज़रूरी होता है, तो भ्रम की स्थिति पैदा हो सकती है.

सीधे तौर पर ऐक्सेस देना

हर बार औपचारिक स्थिति में बदलाव होने पर, मुख्य Driver लूप आगे बढ़ जाता है. अनुबंध के मुताबिक, 'आगे बढ़ने' की स्थितियों का मतलब है कि पहले से कतार में रखे गए सभी SkyValue लुकअप और सबटास्क का समाधान अगला राज्य लागू होने से पहले होता है. कभी-कभी डेलिगेट StateMachine का लॉजिक, फ़ेज़ के शुरुआती चरण में होने की वजह से बेवजह का या बिलकुल उलटा कर देता है. उदाहरण के लिए, अगर डेलिगेट का पहला step SkyKey लुकअप के साथ काम करता है और इसे डेलिगेट करने की स्थिति के लुकअप के साथ दिखाया जा सकता है, तो फ़ेज़ एडवांस उन्हें क्रम में बना देगा. नीचे दिए गए उदाहरण में बताया गया है कि सीधे तौर पर अपने ईमेल खाते का ऐक्सेस देना बेहतर होता है.

class Parent implements StateMachine {
  @Override
  public StateMachine step(Tasks tasks ) {
    tasks.lookUp(new Key1(), this);
    // Directly delegates to `Delegate`.
    //
    // The (valid) alternative:
    //   return new Delegate(this::afterDelegation);
    // would cause `Delegate.step` to execute after `step` completes which would
    // cause lookups of `Key1` and `Key2` to be sequential instead of parallel.
    return new Delegate(this::afterDelegation).step(tasks);
  }

  private StateMachine afterDelegation(Tasks tasks) {
    …
  }
}

class Delegate implements StateMachine {
  private final StateMachine runAfter;

  Delegate(StateMachine runAfter) {
    this.runAfter = runAfter;
  }

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.lookUp(new Key2(), this);
    return …;
  }

  // Rest of implementation.
  …

  private StateMachine complete(Tasks tasks) {
    …
    return runAfter;
  }
}

डेटा फ़्लो

पिछली चर्चा का फ़ोकस कंट्रोल फ़्लो को मैनेज करने पर रहा है. इस सेक्शन में, डेटा वैल्यू को लागू करने के बारे में बताया गया है.

Tasks.lookUp कॉलबैक लागू करना

SkyValue लुकअप में Tasks.lookUp कॉलबैक लागू करने का एक उदाहरण दिया गया है. इस सेक्शन में, एक से ज़्यादा SkyValues को मैनेज करने के तरीके बताए गए हैं. साथ ही, उसे हल करने के तरीके भी बताए गए हैं.

Tasks.lookUp कॉलबैक

Tasks.lookUp तरीका, पैरामीटर के तौर पर sink को कॉलबैक करता है.

  void lookUp(SkyKey key, Consumer<SkyValue> sink);

इसे लागू करने के लिए मुहावरेदार तरीका Java लैम्डा का इस्तेमाल करना होगा:

  tasks.lookUp(key, value -> myValue = (MyValueClass)value);

जिसमें myValue, StateMachine इंस्टेंस का मेंबर वैरिएबल है. हालांकि, StateMachine को लागू करने में Consumer<SkyValue> इंटरफ़ेस के मुकाबले, Lambda को ज़्यादा मेमोरी असाइन करने की ज़रूरत होती है. अगर कई लुकअप से जुड़ी जानकारी साफ़ तौर पर नहीं दिख रही हो, तो भी लैम्डा उपयोगी होता है.

Tasks.lookUp के ओवरलोड को हैंडल करने में भी गड़बड़ी हुई है, जो SkyFunction.Environment.getValueOrThrow से मिलती-जुलती है.

  <E extends Exception> void lookUp(
      SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);

  interface ValueOrExceptionSink<E extends Exception> {
    void acceptValueOrException(@Nullable SkyValue value, @Nullable E exception);
  }

लागू करने का एक उदाहरण नीचे दिया गया है.

class PerformLookupWithError extends StateMachine, ValueOrExceptionSink<MyException> {
  private MyValue value;
  private MyException error;

  @Override
  public StateMachine step(Tasks tasks) {
    tasks.lookUp(new MyKey(), MyException.class, ValueOrExceptionSink<MyException>) this);
    return this::processResult;
  }

  @Override
  public acceptValueOrException(@Nullable SkyValue value, @Nullable MyException exception) {
    if (value != null) {
      this.value = (MyValue)value;
      return;
    }
    if (exception != null) {
      this.error = exception;
      return;
    }
    throw new IllegalArgumentException("Both parameters were unexpectedly null.");
  }

  private StateMachine processResult(Tasks tasks) {
    if (exception != null) {
      // Handles the error.
      …
      return DONE;
    }
    // Processes `value`, which is non-null.
    …
  }
}

अगर बिना गड़बड़ी वाले लुकअप की तरह, StateMachine क्लास में सीधे कॉलबैक लागू किया जाता है, तो lmb के लिए मेमोरी असाइन होती है.

गड़बड़ी को ठीक करने से ज़्यादा जानकारी मिलती है. हालांकि, गड़बड़ियों को ठीक करने के तरीके और सामान्य वैल्यू में ज़्यादा फ़र्क़ नहीं है.

एक से ज़्यादा SkyValues का इस्तेमाल करना

अक्सर SkyValue का एक से ज़्यादा लुकअप ज़रूरी होता है. अक्सर काम करने वाला तरीका SkyValue के प्रकार पर स्विच करना है. नीचे एक उदाहरण दिया गया है, जिसे प्रोटोटाइप प्रोडक्शन कोड से आसान बनाया गया है.

  @Nullable
  private StateMachine fetchConfigurationAndPackage(Tasks tasks) {
    var configurationKey = configuredTarget.getConfigurationKey();
    if (configurationKey != null) {
      tasks.lookUp(configurationKey, (Consumer<SkyValue>) this);
    }

    var packageId = configuredTarget.getLabel().getPackageIdentifier();
    tasks.lookUp(PackageValue.key(packageId), (Consumer<SkyValue>) this);

    return this::constructResult;
  }

  @Override  // Implementation of `Consumer<SkyValue>`.
  public void accept(SkyValue value) {
    if (value instanceof BuildConfigurationValue) {
      this.configurationValue = (BuildConfigurationValue) value;
      return;
    }
    if (value instanceof PackageValue) {
      this.pkg = ((PackageValue) value).getPackage();
      return;
    }
    throw new IllegalArgumentException("unexpected value: " + value);
  }

Consumer<SkyValue> कॉलबैक को साफ़ तौर पर शेयर किया जा सकता है, क्योंकि वैल्यू अलग-अलग तरह की होती हैं. अगर ऐसा नहीं है, तो लैम्डा-आधारित इंप्लीमेंटेशन या सही कॉलबैक लागू करने वाले पूरे इनर-क्लास इंस्टेंस पर लौटना बेहतर होता है.

StateMachine सेकंड के बीच वैल्यू का प्रमोशन किया जा रहा है

अभी तक, इस दस्तावेज़ में सिर्फ़ सबटास्क में काम व्यवस्थित करने के बारे में बताया गया है, लेकिन सबटास्क के लिए भी कॉलर को वापस एक वैल्यू रिपोर्ट करनी होगी. सबटास्क, लॉजिकल रूप से एसिंक्रोनस होते हैं. इसलिए, उनके नतीजों की जानकारी कॉलर को कॉलबैक का इस्तेमाल करके दी जाती है. यह काम करने के लिए, सबटास्क एक ऐसे सिंक इंटरफ़ेस के बारे में बताता है जिसे कंस्ट्रक्टर की मदद से डाला जाता है.

class BarProducer implements StateMachine {
  // Callers of BarProducer implement the following interface to accept its
  // results. Exactly one of the two methods will be called by the time
  // BarProducer completes.
  interface ResultSink {
    void acceptBarValue(Bar value);
    void acceptBarError(BarException exception);
  }

  private final ResultSink sink;

  BarProducer(ResultSink sink) {
     this.sink = sink;
  }

  … // StateMachine steps that end with this::complete.

  private StateMachine complete(Tasks tasks) {
    if (hasError()) {
      sink.acceptBarError(getError());
      return DONE;
    }
    sink.acceptBarValue(getValue());
    return DONE;
  }
}

फिर, StateMachine कॉलर कुछ ऐसा दिखेगा.

class Caller implements StateMachine, BarProducer.ResultSink {
  interface ResultSink {
    void acceptCallerValue(Bar value);
    void acceptCallerError(BarException error);
  }

  private final ResultSink sink;

  private Bar value;

  Caller(ResultSink sink) {
    this.sink = sink;
  }

  @Override
  @Nullable
  public StateMachine step(Tasks tasks) {
    tasks.enqueue(new BarProducer((BarProducer.ResultSink) this));
    return this::processResult;
  }

  @Override
  public void acceptBarValue(Bar value) {
    this.value = value;
  }

  @Override
  public void acceptBarError(BarException error) {
    sink.acceptCallerError(error);
  }

  private StateMachine processResult(Tasks tasks) {
    // Since all enqueued subtasks resolve before `processResult` starts, one of
    // the `BarResultSink` callbacks must have been called by this point.
    if (value == null) {
      return DONE;  // There was a previously reported error.
    }
    var finalResult = computeResult(value);
    sink.acceptCallerValue(finalResult);
    return DONE;
  }
}

पिछला उदाहरण कुछ चीज़ों को दिखाता है. Caller को अपने नतीजों को वापस लागू करना होगा और अपनी Caller.ResultSink के बारे में बताना होगा. Caller, BarProducer.ResultSink कॉलबैक लागू करता है. फिर से शुरू करने पर, processResult यह जांच करता है कि value शून्य है या नहीं, ताकि यह पता चल सके कि कोई गड़बड़ी हुई है या नहीं. किसी सबटास्क या SkyValue लुकअप से आउटपुट स्वीकार करने के बाद, यह व्यवहार का एक आम पैटर्न है.

ध्यान दें कि acceptBarError को लागू करने पर, नतीजे को तुरंत Caller.ResultSink पर फ़ॉरवर्ड कर दिया जाता है, जैसा कि गड़बड़ी बबलिंग के लिए ज़रूरी है.

टॉप-लेवल StateMachine के विकल्पों के बारे में Drivers और Skyफ़ंक्शन से जोड़ने का तरीका बताया गया है.

गड़बड़ी ठीक करना

Tasks.lookUp कॉलबैक और StateMachines के बीच वैल्यू को प्रॉपर्टी करना में पहले से मौजूद गड़बड़ी को ठीक करने के कुछ उदाहरण दिए गए हैं. InterruptedException के अलावा, दूसरे अपवादों को इस्तेमाल नहीं किया जाता. इसके बजाय, इन्हें वैल्यू के तौर पर कॉलबैक को पास किया जाता है. ऐसे कॉलबैक में अक्सर खास या सिमैंटिक होते हैं, जिनमें कोई भी वैल्यू या गड़बड़ी पास की जा रही होती है.

अगले सेक्शन में, Skyframe गड़बड़ी से निपटने के तरीके के साथ एक बारीक, लेकिन अहम इंटरैक्शन के बारे में बताया गया है.

बबलिंग में गड़बड़ी (--nokeep_ running)

गड़बड़ी को बबल करने के दौरान, अनुरोध किए गए सभी SkyValues उपलब्ध न होने पर भी Skyफ़ंक्शन को फिर से शुरू किया जा सकता है. ऐसे मामलों में, Tasks API समझौते की वजह से, बाद की स्थिति कभी भी ऐक्सेस नहीं की जा सकेगी. हालांकि, StateMachine को अब भी इस अपवाद को लागू करना चाहिए.

चाहे अगली स्थिति हुई हो या नहीं, प्रमोशन होना चाहिए. इसलिए, गड़बड़ी मैनेज करने वाले कॉलबैक को यह काम करना होगा. अंदरूनी StateMachine के लिए, ऐसा करने के लिए पैरंट कॉलबैक को चालू करें.

टॉप लेवल StateMachine पर, जो Skyफ़ंक्शन के साथ इंटरफ़ेस करता है, ऐसा करने के लिए ValueOrExceptionProducer के setException तरीके को कॉल किया जा सकता है. इसके बाद, ValueOrExceptionProducer.tryProduceValue अपवाद दिखाएगा, भले ही SkyValues मौजूद न हों.

अगर Driver का सीधे तौर पर इस्तेमाल किया जा रहा है, तो Skyफ़ंक्शन से मिली गड़बड़ियों की जांच करना ज़रूरी है, भले ही मशीन ने प्रोसेसिंग को खत्म न किया हो.

इवेंट हैंडलिंग

जिन Skyफ़ंक्शन को इवेंट भेजने की ज़रूरत होती है उनके लिए, StoredEventHandler को SkyKeyComputeState में इंजेक्ट किया जाता है. इसके बाद, उन्हें StateMachine में इंजेक्ट किया जाता है और फिर उनकी ज़रूरत होती है. पहले भी StoredEventHandler की ज़रूरत थी, क्योंकि Skyframe ने कुछ इवेंट को तब तक नहीं देखा था, जब तक कि उन्हें फिर से नहीं चलाया जाता. हालांकि, बाद में इसे ठीक कर दिया गया. StoredEventHandler इंजेक्शन को सुरक्षित रखा जाता है, क्योंकि यह गड़बड़ी मैनेज करने वाले कॉलबैक से बने इवेंट को आसान बनाता है.

Drivers और Skyफ़ंक्शन को ब्रिज करना

तय किए गए रूट StateMachine से शुरू होने वाले StateMachine को लागू करने का काम Driver को मैनेज किया जाता है. StateMachine, StateMachine बार-बार सबटास्क बना सकता है, इसलिए एक Driver कई सबटास्क को मैनेज कर सकता है. ये सबटास्क एक ट्री संरचना बनाते हैं, जो व्यवस्थित तरीके से एक साथ काम करने की सुविधा का नतीजा है. बेहतर परफ़ॉर्मेंस के लिए, SkyValue के Driver बैच सभी सबटास्क में खोज करते हैं.

Driver के आस-पास कई क्लास बनाई गई हैं. साथ ही, इन एपीआई का इस्तेमाल किया जा रहा है.

public final class Driver {
  public Driver(StateMachine root);
  public boolean drive(SkyFunction.Environment env) throws InterruptedException;
}

Driver, पैरामीटर के तौर पर सिंगल रूट StateMachine लेता है. Driver.drive को कॉल करने की सुविधा, StateMachine को तब तक एक्ज़ीक्यूट करती है, जब तक यह स्काईफ़्रेम को रीस्टार्ट किए बिना चल सकती है. यह StateMachine पूरा होने पर 'सही' दिखाता है और 'गलत' दिखाता है, नहीं तो, यह दिखाता है कि सभी वैल्यू उपलब्ध नहीं थीं.

Driver, StateMachine की समवर्ती स्थिति को बनाए रखता है और यह SkyKeyComputeState में एम्बेड करने के लिए बहुत अच्छी तरह से काम करता है.

Driver को सीधे तौर पर इंस्टैंशिएट किया जा रहा है

StateMachine को लागू करने के तरीके आम तौर पर, अपने नतीजों को कॉलबैक करके बताते हैं. जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है, सीधे Driver को इंस्टैंशिएट किया जा सकता है.

Driver को SkyKeyComputeState लागू करने के साथ ही, इससे जुड़े ResultSink को लागू करने के तरीके में भी एम्बेड किया गया है, ताकि इसे थोड़ा और नीचे बताया जा सके. टॉप लेवल पर, State ऑब्जेक्ट, कंप्यूटेशन के नतीजे के लिए सही रिसीवर है. ऐसा इसलिए, क्योंकि इस बात की गारंटी है कि यह Driver से निकल जाएगा.

class State implements SkyKeyComputeState, ResultProducer.ResultSink {
  // The `Driver` instance, containing the full tree of all `StateMachine`
  // states. Responsible for calling `StateMachine.step` implementations when
  // asynchronous values are available and performing batched SkyFrame lookups.
  //
  // Non-null while `result` is being computed.
  private Driver resultProducer;

  // Variable for storing the result of the `StateMachine`
  //
  // Will be non-null after the computation completes.
  //
  private ResultType result;

  // Implements `ResultProducer.ResultSink`.
  //
  // `ResultProducer` propagates its final value through a callback that is
  // implemented here.
  @Override
  public void acceptResult(ResultType result) {
    this.result = result;
  }
}

नीचे दिया गया कोड ResultProducer को स्केच करता है.

class ResultProducer implements StateMachine {
  interface ResultSink {
    void acceptResult(ResultType value);
  }

  private final Parameters parameters;
  private final ResultSink sink;

  … // Other internal state.

  ResultProducer(Parameters parameters, ResultSink sink) {
    this.parameters = parameters;
    this.sink = sink;
  }

  @Override
  public StateMachine step(Tasks tasks) {
    …  // Implementation.
    return this::complete;
  }

  private StateMachine complete(Tasks tasks) {
    sink.acceptResult(getResult());
    return DONE;
  }
}

फिर, लेज़ी तरीके से नतीजे का हिसाब लगाने के लिए मिला कोड कुछ ऐसा दिख सकता है.

@Nullable
private Result computeResult(State state, Skyfunction.Environment env)
    throws InterruptedException {
  if (state.result != null) {
    return state.result;
  }
  if (state.resultProducer == null) {
    state.resultProducer = new Driver(new ResultProducer(
      new Parameters(), (ResultProducer.ResultSink)state));
  }
  if (state.resultProducer.drive(env)) {
    // Clears the `Driver` instance as it is no longer needed.
    state.resultProducer = null;
  }
  return state.result;
}

Driver को एम्बेड किया जा रहा है

अगर StateMachine कोई वैल्यू बनाता है और कोई अपवाद नहीं बनाता है, तो Driver को एम्बेड करना एक और तरीका है. जैसा कि इस उदाहरण में दिखाया गया है.

class ResultProducer implements StateMachine {
  private final Parameters parameters;
  private final Driver driver;

  private ResultType result;

  ResultProducer(Parameters parameters) {
    this.parameters = parameters;
    this.driver = new Driver(this);
  }

  @Nullable  // Null when a Skyframe restart is needed.
  public ResultType tryProduceValue( SkyFunction.Environment env)
      throws InterruptedException {
    if (!driver.drive(env)) {
      return null;
    }
    return result;
  }

  @Override
  public StateMachine step(Tasks tasks) {
    …  // Implementation.
}

Skyफ़ंक्शन में ऐसा कोड हो सकता है जो नीचे दिया गया हो. यहां State, खास तरह का SkyKeyComputeState फ़ंक्शन है.

@Nullable  // Null when a Skyframe restart is needed.
Result computeResult(SkyFunction.Environment env, State state)
    throws InterruptedException {
  if (state.result != null) {
    return state.result;
  }
  if (state.resultProducer == null) {
    state.resultProducer = new ResultProducer(new Parameters());
  }
  var result = state.resultProducer.tryProduceValue(env);
  if (result == null) {
    return null;
  }
  state.resultProducer = null;
  return state.result = result;
}

StateMachine लागू करने के तरीके में Driver को एम्बेड करना, Skyframe की सिंक्रोनस कोडिंग स्टाइल के लिए बेहतर है.

StateMachines, जो अपवाद पैदा कर सकते हैं

अगर ऐसा नहीं है, तो SkyKeyComputeState एम्बेड की जा सकने वाली ValueOrExceptionProducer और ValueOrException2Producer क्लास हैं. इनमें सिंक करने वाले Skyफ़ंक्शन कोड से मेल खाने के लिए, सिंक्रोनस एपीआई होते हैं.

ValueOrExceptionProducer ऐब्सट्रैक्ट क्लास में ये तरीके शामिल हैं.

public abstract class ValueOrExceptionProducer<V, E extends Exception>
    implements StateMachine {
  @Nullable
  public final V tryProduceValue(Environment env)
      throws InterruptedException, E {
    …  // Implementation.
  }

  protected final void setValue(V value)  {  … // Implementation. }
  protected final void setException(E exception) {  … // Implementation. }
}

इसमें, एम्बेड किया गया Driver इंस्टेंस शामिल है. यह एम्बेडिंग ड्राइवर और Skyफ़ंक्शन के साथ इंटरफ़ेस में, ResultProducer क्लास जैसा दिखता है. यह इसी तरह से दिखता है. ResultSink तय करने के बजाय, इनमें से कोई भी होने पर, setValue या setException को लागू किया जाता है. जब दोनों होते हैं, तो अपवाद को प्राथमिकता दी जाती है. tryProduceValue तरीका एसिंक्रोनस कॉलबैक कोड को सिंक्रोनस कोड में पुल करता है. साथ ही, सेट किए जाने पर अपवाद देता है.

जैसा कि पहले बताया गया है, गड़बड़ी को बबल करते समय कुछ गड़बड़ी हो सकती है. भले ही, मशीन अभी तक पूरी न हुई हो, क्योंकि सभी इनपुट उपलब्ध न होने की वजह से भी गड़बड़ी हो सकती है. इसे ठीक करने के लिए, tryProduceValue मशीन के काम करने से पहले ही, सेट किए गए सभी अपवाद दिखाता है.

Epilogue: आखिरकार कॉलबैक हटाना

StateMachine, एसिंक्रोनस कंप्यूटेशन (कैलकुलेशन) करने का बहुत ही असरदार तरीका है. हालांकि, बॉयलरप्लेट से होने वाली गणना का बेहतर तरीका है. Bazel कोड के कुछ हिस्सों में कॉन्टिन्यूएंट (खास तौर पर, ListenableFuture को पास किए गए Runnable के रूप में) काफ़ी ज़्यादा हैं, लेकिन स्काईफ़ंक्शन के विश्लेषण में ये ज़्यादा नहीं होती हैं. विश्लेषण आम तौर पर सीपीयू से जुड़ा होता है और डिस्क I/O के लिए, कोई बेहतर एसिंक्रोनस एपीआई नहीं होता. ऐसे में कॉलबैक को ऑप्टिमाइज़ करना बेहतर होगा, क्योंकि उनमें लर्निंग कर्व और उन्हें पढ़ने में दिक्कतें होती हैं.

Java वर्चुअल थ्रेड एक अच्छा विकल्प है. कॉलबैक लिखने के बजाय, हर चीज़ को सिंक्रोनस, ब्लॉक करने वाले कॉल से बदल दिया जाता है. ऐसा इसलिए होता है, क्योंकि प्लैटफ़ॉर्म थ्रेड की तरह, वर्चुअल थ्रेड रिसॉर्स को लिंक करना सस्ता माना जाता है. हालांकि, वर्चुअल थ्रेड के साथ भी, आसान सिंक्रोनस ऑपरेशन को थ्रेड बनाने और सिंक करने की सुविधा के साथ बदलना बहुत महंगा होता है. हमने StateMachine से Java वर्चुअल थ्रेड पर माइग्रेशन किया और वे बहुत धीरे लोड हुए. इस वजह से शुरू से अंत तक के विश्लेषण में करीब तीन गुना बढ़ोतरी हुई. वर्चुअल थ्रेड अब भी झलक दिखाने वाली सुविधा हैं. इसलिए, यह हो सकता है कि परफ़ॉर्मेंस बेहतर होने पर, यह माइग्रेशन बाद में किया जा सकता है.

दूसरा तरीका यह है कि लूम कोरूटीन उपलब्ध होने पर, उनका इंतज़ार किया जाए. यहां फ़ायदा यह है कि कोऑपरेटिव मल्टीटास्किंग का इस्तेमाल करके सिंक्रोनाइज़ेशन ओवरहेड कम किया जा सकता है.

अगर सब कुछ नहीं होता है, तो बाइट कोड को फिर से लिखना एक अच्छा विकल्प हो सकता है. ज़रूरत के मुताबिक ऑप्टिमाइज़ेशन के साथ, हाथ से लिखे हुए कॉलबैक कोड तक पहुंचने वाला परफ़ॉर्मेंस हासिल किया जा सकता है.

अन्य जानकारी

कॉलबैक हेल

कॉलबैक नर, एसिंक्रोनस कोड में एक बड़ी समस्या है. यह कॉलबैक का इस्तेमाल करती है. इसकी वजह यह है कि अगले चरण का डेटा, पिछले चरण में ही नेस्ट किया जाता है. अगर कई चरण हैं, तो नेस्ट करना काफ़ी गहरा हो सकता है. कंट्रोल फ़्लो के साथ जोड़े जाने पर, कोड को मैनेज नहीं किया जा सकता.

class CallbackHell implements StateMachine {
  @Override
  public StateMachine step(Tasks task) {
    doA();
    return (t, l) -> {
      doB();
      return (t1, l2) -> {
        doC();
        return DONE;
      };
    };
  }
}

नेस्ट किए गए लागू करने का एक फ़ायदा यह भी है कि बाहरी चरण के स्टैक फ़्रेम को सुरक्षित रखा जा सकता है. Java में, कैप्चर किए गए लैम्डा वैरिएबल प्रभावी रूप से अंतिम होने चाहिए, इसलिए इस तरह के वैरिएबल का इस्तेमाल करना मुश्किल हो सकता है. डीप नेस्टिंग से बचने के लिए, नीचे बताए गए तरीके के मुताबिक लैम्डा के बजाय, 'कंटिन्यूशन' के तौर पर रेफ़रंस देना ज़रूरी है.

class CallbackHellAvoided implements StateMachine {
  @Override
  public StateMachine step(Tasks task) {
    doA();
    return this::step2;
  }

  private StateMachine step2(Tasks tasks) {
    doB();
    return this::step3;
  }

  private StateMachine step3(Tasks tasks) {
    doC();
    return DONE;
  }
}

runAfter इंजेक्शन पैटर्न का इस्तेमाल बहुत बारीकी से करने पर भी कॉलबैक हेल हो सकता है. हालांकि, एक के बाद एक इंजेक्शन लगाने से इससे बचा जा सकता है.

उदाहरण: चेन किए गए SkyValue लुकअप

अक्सर ऐसा होता है कि ऐप्लिकेशन लॉजिक के लिए SkyValue लुकअप की डिपेंडेंट चेन की ज़रूरत होती है. उदाहरण के लिए, अगर दूसरा SkyValue पहले SkyValue पर निर्भर करता है. इस छोटे-से फ़ॉर्मैट के बारे में सोचने पर, एक जटिल और नेस्ट की गई कॉलबैक संरचना बन जाएगी.

private ValueType1 value1;
private ValueType2 value2;

private StateMachine step1(...) {
  tasks.lookUp(key1, (Consumer<SkyValue>) this);  // key1 has type KeyType1.
  return this::step2;
}

@Override
public void accept(SkyValue value) {
  this.value1 = (ValueType1) value;
}

private StateMachine step2(...) {
  KeyType2 key2 = computeKey(value1);
  tasks.lookup(key2, this::acceptValueType2);
  return this::step3;
}

private void acceptValueType2(SkyValue value) {
  this.value2 = (ValueType2) value;
}

हालांकि, जारी रखने को तरीकों के रेफ़रंस के तौर पर बताया गया है, इसलिए कोड सभी स्टेट ट्रांज़िशन में प्रोसेस होता दिखता है: step2, step1 के बाद आता है. ध्यान दें कि यहां, value2 असाइन करने के लिए लैम्डा का इस्तेमाल किया गया है. इससे कोड का क्रम, कैलकुलेशन के ऊपर से नीचे के क्रम से मैच करता है.

अन्य सलाह

रीडेबिलिटी: एक्ज़ीक्यूशन ऑर्डरिंग

कॉन्टेंट को बेहतर तरीके से पढ़ा जा सके, इसके लिए StateMachine.step लागू करने और उसे लागू करने के क्रम में उसी जगह लागू करने की कोशिश करें जहां कोड में पास किया गया हो. हालांकि, जिन जगहों पर कंट्रोल फ़्लो की सीमा है वहां ऐसा करना हमेशा संभव नहीं होता. ऐसे मामलों में, अतिरिक्त टिप्पणियों से मदद मिल सकती है.

उदाहरण: चेन्ड SkyValue लुकअप में, इसे हासिल करने के लिए इंटरमीडिएट तरीके का रेफ़रंस बनाया जाता है. हालांकि, पढ़ने में आसानी होती है और यह काम की छोटी-मोटी चीज़ों को दिखाता है.

जनरेशनल हाइपोथीसिस

मध्यम अवधि वाले Java ऑब्जेक्ट, Java की गारबेज कलेक्टर की जनरेशन के अनुमान को तोड़ते हैं, जिसे कुछ समय तक रहने वाले ऑब्जेक्ट या हमेशा रहने वाले ऑब्जेक्ट को संभालने के लिए डिज़ाइन किया गया है. परिभाषा के मुताबिक, SkyKeyComputeState में मौजूद ऑब्जेक्ट इस अनुमान का उल्लंघन करते हैं. ऐसे ऑब्जेक्ट, जिनमें अब भी चल रहे सभी StateMachines के बनाए गए ट्री होते हैं और जो Driver पर रूट किए गए हैं, उनका निलंबन खत्म होने पर बीच का समय होता है. एसिंक्रोनस कंप्यूटेशन के पूरा होने का इंतज़ार किया जाता है.

JDK19 में यह समस्या कम लगती है, लेकिन StateMachine का इस्तेमाल करने पर, कभी-कभी जीसी समय में बढ़ोतरी दिख सकती है. भले ही, असल में कचरा कम हुआ हो. StateMachine की लाइफ़ बची हुई है यानी उन्हें पुराने वर्शन में अपग्रेड किया जा सकता है. इस वजह से, इन्हें ज़्यादा तेज़ी से भरा जा सकता है. ऐसे में, इन्हें हटाने के लिए ज़्यादा महंगे मेजर या पूरे जीसी की ज़रूरत होगी.

शुरुआत में, StateMachine वैरिएबल का इस्तेमाल कम से कम करना ज़रूरी होता है. हालांकि, हमेशा ऐसा करना मुमकिन नहीं होता. उदाहरण के लिए, अगर एक से ज़्यादा राज्यों में वैल्यू की ज़रूरत हो. जहां संभव होता है, लोकल स्टैक step वैरिएबल यंग जनरेशन के वैरिएबल होते हैं और ये बहुत असरदार होते हैं.

StateMachine वैरिएबल के लिए, चीज़ों को सबटास्क में बांटना और StateMachines के बीच वैल्यू को प्रॉपर्टी करने के लिए सुझाए गए पैटर्न का पालन करना भी मददगार होता है. ध्यान रखें कि पैटर्न का पालन करते समय, सिर्फ़ बच्चे StateMachine के रेफ़रंस, पैरंट StateMachines के रेफ़रंस में शामिल हो सकते हैं और इसके उलट भी नहीं हो सकते. इसका मतलब है कि जब बच्चे, नतीजे के कॉलबैक का इस्तेमाल करके माता-पिता को पूरी जानकारी देते हैं और अपडेट करते हैं, तो बच्चे स्वाभाविक रूप से दायरे से बाहर हो जाते हैं और GC की ज़रूरी शर्तें पूरी कर लेते हैं.

आखिर में, कुछ मामलों में StateMachine वैरिएबल की ज़रूरत शुरुआत की स्थितियों में होती है, लेकिन बाद की स्थितियों में नहीं. जब यह पता चल जाता है कि बड़े ऑब्जेक्ट की अब ज़रूरत नहीं है, तो उनके रेफ़रंस को शून्य करना फ़ायदेमंद हो सकता है.

नाम रखने की स्थितियां

किसी तरीके को नाम देते समय, आम तौर पर उससे जुड़े व्यवहार के लिए किसी तरीके को नाम दिया जा सकता है. StateMachine में इसे करना आसान नहीं है, क्योंकि कोई स्टैक नहीं है. उदाहरण के लिए, मान लें कि तरीका foo किसी सब-तरीका bar को कॉल करता है. StateMachine में, इसे foo स्टेट क्रम के बाद bar में बदला जा सकता है. foo में अब यह व्यवहार bar शामिल नहीं है. इस वजह से, राज्यों के लिए तरीकों के नाम का दायरा छोटा होने की संभावना होती है, जिससे स्थानीय तौर-तरीकों का पता चलता है.

समवर्ती ट्री का डायग्राम

यहां दिए गए डायग्राम में स्ट्रक्चर्ड तरीके से बताए गए तरीके दिखाए गए हैं, जो पेड़ों की संरचना को बेहतर तरीके से दिखाते हैं. ब्लॉक एक छोटा पेड़ बनाते हैं.

स्ट्रक्चर्ड कॉनकरेंसी 3D


  1. वैल्यू उपलब्ध न होने पर Skyframe को शुरुआत से रीस्टार्ट करने के तरीके के उलट.

  2. ध्यान दें कि step को InterruptedException को फेंकने की अनुमति है, लेकिन उदाहरणों में इसे छोड़ दिया जाता है. Bazel कोड में कुछ ऐसे कम तरीके हैं जो इस अपवाद को फेंकते हैं. ये तरीके Driver तक लागू होते हैं और बाद में StateMachine चलाते हैं. ज़रूरत न होने पर यह एलान करना ठीक रहता है कि इसे फेंक दिया जाएगा.

  3. एक साथ काम करने वाले सबटास्क ConfiguredTargetFunction से प्रेरित थे, जो हर डिपेंडेंसी के लिए स्वतंत्र काम करते हैं. सभी डिपेंडेंसी को एक साथ प्रोसेस करने वाले कॉम्प्लेक्स डेटा स्ट्रक्चर में बदलाव करने के बजाय, गड़बड़ियां पैदा करने के लिए हर डिपेंडेंसी का अपना अलग StateMachine होता है.

  4. एक चरण में कई tasks.lookUp कॉल को एक साथ बैच में भेजा जाता है. एक साथ कई सबटास्क में होने वाले लुकअप से अतिरिक्त बैचिंग बनाई जा सकती है.

  5. यह सैद्धांतिक तौर पर, Java की स्ट्रक्चर्ड कंकरंसी की सुविधा jeps/428 से मिलता-जुलता है.

  6. ऐसा करना, एक धागे को आकार देने और एक क्रम में एक कंपोज़िशन बनाने के लिए जोड़ने जैसा है.