Skyframe StateMachines Kılavuzu

Sorun bildir Kaynağı göster Gece · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Genel Bakış

Skyframe StateMachine, başka bir yerde bulunan deyapılandırılmış bir işlev nesnesi yığmaktır. Gerektiğinde esneklik ve değerlendirmeyi hem de1 gerekli değerler hemen kullanıma sunulmaz ancak eşzamansız olarak hesaplanır. İlgili içeriği oluşturmak için kullanılan StateMachine, beklemedeyken bir iş parçacığı kaynağını bağlayamaz, ancak bunun yerine şunları yapması gerekir: askıya alınabilir ve devam ettirilebilir. Bu şekilde imha işlemi, yeniden girişin açık bir şekilde ortaya çıkmasını sağlar Böylece önceki hesaplamaların atlanabilmesini sağlayabilirsiniz.

StateMachine öğeleri; dizileri, dalları, yapılandırılmış mantıksal ifadeyi ifade etmek için kullanılabilir. ve Skyframe etkileşimi için özel olarak tasarlanır. StateMachine öğeleri, daha büyük StateMachine öğeleri halinde oluşturulabilir ve paylaşılabilir. alt StateMachine Eşzamanlılık yapısı gereği her zaman hiyerarşiktir tamamen mantıksaldır. Eşzamanlı her alt görev, paylaşılan tek bir üst öğede çalışır SkyFunction iş parçacığı.

Giriş

Bu bölüm, şu konumda bulunan StateMachine'ları kısaca motive eder ve tanıtır: java.com.google.devtools.build.skyframe.state paketinden yararlanın.

Skyframe'in yeniden başlatılmasına kısa bir giriş

Skyframe, bağımlılık grafiklerinin paralel olarak değerlendirildiği bir çerçevedir. Grafikteki her düğüm bir SkyFunction'ın değerlendirmesine karşılık gelir ve SkyKey, parametrelerini ve SkyValue'yu sonucunu belirtiyor. İlgili içeriği oluşturmak için kullanılan SkyFunction'ın SkyKey ile SkyValues'u arayabilmesi, ek SkyFunctions'ın yinelemeli ve paralel değerlendirmesini tetikliyor. Şunun yerine: SkyValue'nun henüz gerçekleştirilmediği bir işlem olduğunda, bir iş parçacığının bağlanmasına hesaplamanın bazı alt grafikleri tamamlanmadığı için hazır, SkyFunction bir null getValue yanıtı gözlemliyor ve null değerini döndürecek SkyValue yerine, eksik girişler nedeniyle eksik olduğunu belirten bir işaret oluşturabilir. Skyframe, daha önce istenen tüm SkyValues değerleri olduğunda SkyFunctions'ı yeniden başlatır kullanılabilir hale gelir.

SkyKeyComputeState kullanıma sunulmadan önce, geleneksel iletişim yöntemi hesaplamanın tamamen yeniden çalıştırılmasıydı. Bu, ikinci dereceden ve bu şekilde yazılan işlevler en sonunda tamamlanır çünkü her tekrar, null sonuç döndüren arama sayısı daha az. SkyKeyComputeState ile şunları yapabilirsiniz: kontrol noktası verilerini SkyFunction ile ilişkilendirerek yeniden hesaplamadır.

StateMachine, SkyKeyComputeState içinde yaşayan ve SkyFunction yeniden başlatıldığında hemen hemen tüm yeniden hesaplamaları ( SkyKeyComputeState, askıya alma ve devam ettirme özelliğini uygulayarak önbellekten çıkmaz) yürütme kancalarıdır.

SkyKeyComputeState içindeki durum bilgili hesaplamalar

Nesne odaklı tasarım açısından bakıldığında, depolama alanı SkyKeyComputeState içindeki işlemsel nesneleri içerir. Java'da, nesne taşıyan bir davranışın asgari tanımı bir işlevsel arayüze sahip olduğunu varsayalım. Bir StateMachine şu tanım2:

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

Tasks arayüzü, SkyFunction.Environment arayüzüne benzer ancak eşzamansızlık için tasarlanmıştır ve mantıksal olarak eşzamanlı alt görevler için destek ekler3.

step işlevinin döndürülen değeri başka bir StateMachine ve spesifikasyona izin veriyor. adımlardan oluşur. step,DONE StateMachine tamamlandı. Örneğin:

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;
  }
}

aşağıdaki çıkışa sahip bir StateMachine öğesini açıklar.

hello
world

this::step2 yöntem referansının da aynı zamanda bir StateMachine step2 StateMachine ürününün işlevsel arayüz tanımına uygun. Yöntem referanslar, bir sonraki durumu belirtmenin en yaygın yoludur StateMachine

Askıya alma ve devam ettirme

Sezgisel olarak, bir hesaplamayı StateMachine bir örneği askıya almak ve devam ettirmek için gereken kancaları hesaplamadır. StateMachine.step geri döndüğünde açık bir askıya alma işlemi uygulanır puan. Döndürülen StateMachine değeri tarafından belirtilen devamlılık açık resume noktası. Böylece, yeniden hesaplama yapmaktan kaçınabilirsiniz çünkü işlemeye kaldığımız yerden devam edebilirsiniz.

Geri çağırmalar, devamlılıklar ve eşzamansız hesaplama

Teknik açıdan bakıldığında StateMachine, bir devamlılık görevi görür ve yürütülecek sonraki hesaplamadır. StateMachine, engellemek yerine şunları yapabilir: kendi isteğiyle askıya alma; step kontrolü tekrar Driver örneğine döner. Driver şunları yapabilir: ardından hazır bir StateMachine moduna geçin veya kontrolü tekrar Skyframe'e bırakın.

Geleneksel olarak geri çağırmalar ve devamlılık işlemleri tek bir kavramda kullanılır. Ancak StateMachine öğeleri arasında bir ayrım vardır.

  • Geri çağırma: Eşzamansız sonucun nerede depolanacağını açıklar hesaplamadır.
  • Devam: Bir sonraki yürütme durumunu belirtir.

Eşzamansız bir işlem çağrılırken geri çağırmalar gerekir. Diğer bir deyişle, gerçek işlem, yöntem çağrıldıktan hemen sonra gerçekleşmez (örneğin, olduğunu doğrulayabilirsiniz. Geri çağırma işlevleri mümkün olduğunca basit olmalıdır.

Devamlar, StateMachine ve StateMachine değerlerinin döndürdüğü değerlerdir. tümü eşzamansız bir şekilde izleyen karmaşık yürütme sürecini özetler çözülür. Bu yapılandırılmış yaklaşım planınızın karmaşıklığını geri arama özelliğini kullanabilirsiniz.

Görevler

Tasks arayüzü, StateMachine kullanıcılarına SkyValues'u aramak için bir API sağlar ve eşzamanlı alt görevler planlamak için kullanılabilmesidir.

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 aramaları

StateMachine, SkyValues'u aramak için Tasks.lookUp aşırı yükleme kullanıyor. Bunlar: SkyFunction.Environment.getValue ve benzeri SkyFunction.Environment.getValueOrThrow ve benzer istisna işlemeye sahip anlambilim. Uygulama, aramayı hemen gerçekleştirmez bunun yerine, mümkün olduğunca fazla aramayı4 toplu olarak işler. Değer hemen kullanılamayabilir (örneğin, Skyframe yeniden başlatılması gerektiğinde, Bu nedenle, çağrı, bir geri çağırma kullanarak sonuçta elde edilecek değerle ne yapılacağını belirtir.

StateMachine işlemci (Driver ve köprü SkyFrame), değerin önceden görüntülendikten sonra mevcut olacağını garanti eder durumu başlar. Bir örnek aşağıda verilmiştir.

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;
  }
}

Yukarıdaki örnekte, ilk adım new Key() için bir arama yapar. tüketici olarak this. Bu mümkündür çünkü DoesLookup Consumer<SkyValue>.

Sözleşme uyarınca, bir sonraki DoesLookup.processValue eyalet başlamadan önce DoesLookup.step aramaları tamamlandı. Dolayısıyla value, şu durumlarda kullanılabilir: processValue üzerinden erişilir.

Alt görevler

Tasks.enqueue, mantıksal olarak eşzamanlı alt görevlerin yürütülmesini ister. Alt görevler de StateMachine görevleri ve normal StateMachine görevleriyle her şeyi yapabilir. daha fazla alt görev oluşturma veya SkyValue'ları arama gibi özellikleri kullanabilirsiniz. lookUp gibi, durum makine sürücüsü de tüm alt görevlerin tamamlamanız gerekir. Bir örnek aşağıda verilmiştir.

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 ve Subtask2 mantıksal olarak eşzamanlı olsa da her şey tek iş parçacığı, dolayısıyla "eşzamanlı" i güncellemesinin hiçbirine gerek yok senkronize edilir.

Yapılandırılmış eşzamanlılık

Her lookUp ve enqueue bir sonrakine geçmeden önce çözülmesi gerektiğinden eş zamanlılığın doğal olarak ağaç yapılarıyla sınırlı olduğu anlamına gelir. İnsanların aşağıda gösterildiği gibi hiyerarşik5 eşzamanlılık oluşturmak mümkündür örneğine bakalım.

Yapılandırılmış Eşzamanlılık

Eşzamanlılık yapısının bir ağaç oluşturduğunu UML'den anlamak zordur. Mevcut alternatif bir görünüm ağaç yapısına bakalım.

Yapılandırılmamış Eşzamanlı

Yapılandırılmış eşzamanlılık hakkında akıl yürütebilmek çok daha kolaydır.

Düzen ve kontrol akışı kalıpları

Bu bölümde, birden fazla StateMachine öğesinin nasıl oluşturulabileceğine dair örnekler sunulmaktadır ve belirli kontrol akışı sorunlarına çözümler.

Sıralı durumlar

Bu, en yaygın ve basit kontrol akışı kalıbıdır. Örnek: Bu, içindeki durum bilgili hesaplamalar bölümünde SkyKeyComputeState değerleridir.

Dallara ayırma

StateMachine eyaletlerindeki dallar, farklı kullanıcılar döndürerek sağlanabilir. değerlerini normal Java kontrol akışı kullanarak oluşturun.

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;
  }
  …
}

Bazı şubelerin erken tamamlanmak için DONE iade etmesi çok yaygın bir durumdur.

Gelişmiş sıralı bileşim

StateMachine kontrol yapısı belleksiz olduğundan StateMachine paylaşılıyor. alt görev tanımları bazen garip olabiliyor. M1 ve M2; StateMachine, S ve diğer benzer ölçekleri paylaşan StateMachine M1 ve M2, <A, S, B> ve Sırasıyla <X, S, Y>. Sorun, S'nin aşağıdakileri yapıp yapmayacağını bilmemesidir: tamamlandıktan sonra B veya Y'ye devam eder ve StateMachine'lar çağrı yığını. Bu bölümde, bunu başarmaya yönelik bazı teknikler incelenmektedir.

Terminal sıra öğesi olarak StateMachine

Bu ise ilk ortaya çıkan sorunu çözmez. Yalnızca sıralı paylaşılan StateMachine dizide terminal olduğunda kompozisyon oluşturun.

// 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 karmaşık bir durum makinesi olsa bile bu yöntem işe yarar.

Sıralı beste için alt görev

Sıraya alınan alt görevlerin bir sonraki durumdan önce tamamlanması garanti edildiğinden, alt görev mekanizmasının hafif kötüye kullanımı6 mümkündür.

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 yerleştirme

Bazı durumlarda, başka koşullar nedeniyle Tasks.enqueue kötüye kullanımı S öncesinde tamamlanması gereken paralel alt görevler veya Tasks.lookUp çağrı yürütülür. Bu durumda S içine runAfter parametresi ekleme işlemi şunun için kullanılabilir: S'yi bundan sonra ne yapacağı konusunda bilgilendirir.

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;
  }
}

Bu yaklaşım, alt görevleri kötüye kullanmaktan daha nettir. Ancak, bunu da uygulamak serbest bir şekilde, örneğin birden fazla StateMachine öğesini runAfter ile iç içe yerleştirerek Callback Hell'e giden yolda. Daha iyi sonuç verir. yerine normal sıralı durumlara sahip runAfter öğeleri.

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

şunlarla değiştirilebilir.

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

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

Yasak alternatifi: runAfterUnlessError

Daha önceki bir taslakta, iptal edecek bir runAfterUnlessError kullanmayı düşünüyorduk erken hatalardan kurtulun. Hataların genellikle hatalarla uğraşarak Biri runAfter referansı olan StateMachine tarafından olmak üzere iki kez kontrol edildi ve bir kez runAfter makinesinin kendisi tarafından yapıldı.

Biraz düşündükten sonra, kodun tek tipliğinin daha fazla olduğuna karar verdik. daha önemlidir. Yalnızca URL'nin runAfter mekanizması Her zaman hata kontrolü gerektiren tasks.enqueue mekanizması.

Doğrudan yetki

Resmi durum geçişi her yapıldığında ana Driver döngüsü ilerler. Sözleşmeye göre, ilerleme durumu, daha önce sıraya alınan tüm SkyValue'nun önceki durum yürütülmeden önce aramalar ve alt görevler çözülür. Bazen mantığın StateMachine yetki verilmişse aşama aşama ilerlemesini gereksiz kılar veya olumsuz etki yaratabilir. Örneğin, yetki verilen kullanıcının ilk step'si işlem gerçekleştirirse Yetki veren duruma ilişkin aramalarla paralel yapılabilecek SkyKey aramaları bir aşama avansı bunları sıralı hale getirir. Bu kurstan sonra aşağıdaki örnekte gösterildiği gibi doğrudan yetki verilmelidir.

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;
  }
}

Veri akışı

Bir önceki tartışmada kontrol akışının yönetilmesine odaklandık. Bu bölümünde, veri değerlerinin yayılımı açıklanmaktadır.

Tasks.lookUp geri çağırma uygulama

SkyValue'da bir Tasks.lookUp geri çağırması uygulamaya ilişkin bir örnek bulunmaktadır arama. Bu bölümde gerekçe sunulur ve yaklaşımlarını öğreneceksiniz.

Tasks.lookUp geri arama

Tasks.lookUp yöntemi, parametre olarak bir geri çağırma (sink) alır.

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

Deyimsel yaklaşım, şunu uygulamak için bir Java lambdası kullanmak olur:

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

myValue, şunu yapan StateMachine örneğinin üye değişkenidir: araması yapın. Ancak lambda, Google'ın açık kaynak koduna kıyasla Consumer<SkyValue> arayüzünü StateMachine uygulamasında uygulama bazı ipuçları vereceğim. Lambda, bir dizi arama terimini içeren birden fazla arama muğlak olur.

Ayrıca, 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);
  }

Aşağıda örnek bir uygulama gösterilmiştir.

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.
    …
  }
}

Hata işleme gerektirmeyen aramalarda olduğu gibi doğrudan StateMachine sınıfını kullanmak uygulanması, lamba için bir bellek tahsisi kaydeder.

Hata işleme biraz daha ayrıntılı bilgi sağlar, ancak aslında hataların yayılması ile normal değerler arasında çok fazla fark yoktur.

Birden çok SkyValues kullanma

Genellikle birden çok SkyValue araması yapılması gerekir. Çevik yaklaşımın SkyValue türünü açacağım. Aşağıdaki örnekteki gibi , prototip üretim kodundan basitleştirilmiştir.

  @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> geri çağırma uygulaması açık bir şekilde paylaşılabilir çünkü değer türleri farklı. Böyle bir durum söz konusu değilse lambda tabanlı uygulamalar veya uygun geri çağırmalar yapılmalıdır.

Değerleri StateMachine saniyeler arasında yayma

Şu ana kadar bu belgede yalnızca bir alt görevdeki işlerin nasıl düzenleneceği anlatılmıştı ancak alt görevlerin çağrı yapana bir değer bildirmesi de gerekir. Alt görevler mantıksal olarak eşzamansız olduğunda, sonuçları geri arama. Bunun işe yaraması için alt görev, enjekte edilir.

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;
  }
}

Bu durumda arayan (StateMachine) aşağıdaki gibi görünür.

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;
  }
}

Yukarıdaki örnekte birkaç şey açıklanmaktadır. Caller, kendi Caller.ResultSink değerini tanımlar. Caller, BarProducer.ResultSink geri arama. processResult yeniden başlatıldıktan sonra value, bir hata oluşup oluşmadığını belirlemek için null. Bu, sıklıkla karşılaşılan bir davranıştır bir alt görevden veya SkyValue aramasından çıktı kabul ettikten sonra tekrar aynı kalıbı kapatamaz.

acceptBarError uygulandığında, sonucun Hata baloncuklarının gerektirdiği şekilde Caller.ResultSink.

Üst düzey StateMachine için alternatifler Drivers ve SkyFunctions ile köprü oluşturma.

Hata işleme

Tasks.lookUp ürününde hata işlemeyle ilgili birkaç örnek bulabilirsiniz. geri çağırma işlevleri ve Değerleri StateMachines) İstisnalar, InterruptedException atılmaz, bunun yerine atlatılır geri çağırmalar olabilir. Bu tür geri çağırmaların çoğu zaman özel veya anlamı vardır. hatadan veya değerden tam olarak biri mi?

Sonraki bölümde Skyframe ile kurulan küçük ancak önemli bir etkileşim ele alacağız.

Hata kabarıklığı (--nokeep_continue)

Hata balonları oluşurken, istenenlerin tümü olmasa bile bir SkyFunction yeniden başlatılabilir SkyValues kullanılabilir. Bu gibi durumlarda, sonraki durum hiçbir zaman Tasks API sözleşmesi nedeniyle ulaşıldı. Ancak StateMachine, istisnayı dağıtmaya devam eder.

Yayılım, sonraki duruma ulaşılıp ulaşılmadığına bakılmaksızın gerçekleşmesi gerektiğinden, geri çağırma hatası, bu görevi gerçekleştirmelidir. Dahili bir StateMachine için, Bunun için üst geri çağırma işlevi çağrılır.

SkyFunction ile arayüz oluşturan üst düzey StateMachine'de bu komut, ValueOrExceptionProducer öğesinin setException yöntemi çağrılarak yapılır. Bu durumda ValueOrExceptionProducer.tryProduceValue, istisnayı geçersiz kılacak olsa da SkyValue'lar eksik.

Driver doğrudan kullanılıyorsa her zaman makine tamamlanmamış olsa bile SkyFunction'dan hatalar yayıldı işleniyor.

Etkinlik İşleme

Etkinlik yayınlaması gereken SkyFunctions için bir StoredEventHandler eklenir SkyKeyComputeState bölümüne ve daha fazla eklenerek StateMachine oluşturabilirsiniz. Geçmişte, Skyframe'in düşmesi nedeniyle StoredEventHandler gerekiyordu belirli etkinlikler yeniden oynatılmaz. Ancak bu sorun daha sonra giderilmiştir. StoredEventHandler ekleme işlemi, şunu basitleştirdiği için korunuyor: geri çağırmaların işlenmesinden kaynaklanan etkinliklerin yayınlanması.

Driver'ler ve SkyFunctions'a köprü

Driver, StateMachine öğelerinin yürütülmesini yönetmekten sorumludur, belirtilen bir kök StateMachine ile başlıyor. StateMachine kullanıcıları tekrarlayan bir şekilde alt görevleri sıraya koyun StateMachines, tek bir Driver yönetebilir alt göreve başladı. Bu alt görevler bir ağaç yapısı oluşturur. Yapılandırılmış eşzamanlılık. Driver, SkyValue'yu toplu olarak işler alt görevler için arama yaparak verimlilik artışı sağlar.

Aşağıdaki API'yi kullanarak Driver etrafında oluşturulmuş çok sayıda sınıf bulunmaktadır.

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

Driver, parametre olarak tek bir kök StateMachine alır. Telefon etme Driver.drive, bir StateMachine yönergesi olmadan mümkün olan en fazla şekilde StateMachine yürütür. Skyframe yeniden başlatıldı. StateMachine tamamlandığında "true" (doğru), "false" (yanlış) değerini döndürür aksi takdirde tüm değerlerin mevcut olmadığını gösterir.

Driver, StateMachine öğesinin eşzamanlı durumunu koruyor ve bu iyi bir değer SkyKeyComputeState dosyasına yerleştirilmeye uygundur.

Driver öğesini doğrudan örneklendirme

StateMachine uygulamaları, sonuçlarını geleneksel olarak daha fazla bilgi edindiniz. Driver, örneği inceleyelim.

Driver, ile birlikte SkyKeyComputeState uygulamasına yerleştirilmiş. biraz daha ileride tanımlanacak karşılık gelen ResultSink uygulanması aşağı tüketim. Üst düzeyde, State nesnesi Driver tarihinde sona ereceği garanti edildiği için yapılan hesaplamadan kaynaklanan sonucuna varılmıştır.

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;
  }
}

Aşağıdaki kod ResultProducer taslağını çiziyor.

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;
  }
}

Daha sonra, sonucu geç hesaplama kodu aşağıdaki gibi görünebilir.

@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 öğe yerleştiriliyor

StateMachine bir değer üretir ve hiçbir istisna sağlamazsa Driver, aşağıdaki örnekte gösterildiği gibi olası başka bir uygulamadır.

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.
}

SkyFunction aşağıdaki gibi koda sahip olabilir (State işleve özgü SkyKeyComputeState türü).

@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;
}

Driver özelliğinin StateMachine uygulamasına yerleştirilmesi aşağıdakiler için daha uygundur: Skyframe'in eşzamanlı kodlama stili.

İstisna oluşturabilecek StateMachines

Aksi takdirde, SkyKeyComputeState-yerleştirilebilir ValueOrExceptionProducer var ve eşleştirilecek eşzamanlı API'lere sahip ValueOrException2Producer sınıf senkronize SkyFunction kodu.

ValueOrExceptionProducer soyut sınıfı aşağıdaki yöntemleri içerir.

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. }
}

Yerleştirilmiş bir Driver örneği içerir ve Sürücü yerleştirme ve arayüzlerde ResultProducer sınıfı SkyFunction ile benzer şekilde ekleyebilirsiniz. Bir ResultSink tanımlamak yerine, uygulamaları, bunlardan biri gerçekleştiğinde setValue veya setException yöntemini çağırır. Her ikisi de gerçekleştiğinde istisna, öncelikli olur. tryProduceValue yöntemi eşzamansız geri çağırma kodunu eşzamanlı koda bağlar ve istisna olarak çalışır.

Daha önce de belirtildiği gibi, hata şişirme sırasında bir hatanın veya tüm girişler kullanılabilir olmadığından makine henüz bitmemiş olsa bile Alıcı: buna dahil değilse tryProduceValue, ayarlanmış tüm istisnaları, üzerine konuşalım.

Son söz: Geri çağırmaları sonunda kaldırma

StateMachine'lar, oldukça verimli ancak yoğun bir performans sunma yöntemidir. eşzamansız hesaplama. Devamlılıklar (özellikle Runnable biçiminde) ListenableFuture) Bazel kodunun belirli kısımlarında yaygın olarak kullanılır. ancak SkyFunctions analizinde yaygın değildir. Analiz çoğunlukla CPU'ya bağlıdır ve Disk G/Ç için verimli eşzamansız API yok. En sonunda bu, geri arama optimizasyonu açısından iyidir çünkü bir öğrenme eğrisi daha iyi anlamanızı sağlar.

En umut verici alternatiflerden biri, Java sanal iş parçacıklarıdır. Şunun yerine: gerektiğinde, her şeyin yerini eşzamanlı veya düzgün çalışan engelleme çağrısının en iyi yoludur. Bu, bir sanal iş parçacığı kaynağını (makine öğrenimini platform iş parçacığının ucuz olması gerekir. Ancak sanal ileti dizilerinde bile Basit eşzamanlı işlemleri iş parçacığı oluşturma ve senkronizasyon ile değiştirme ilkel öğeler çok pahalıdır. StateMachine sn. > Java sanal iş parçacıkları büyüklükte daha yavaştı. Bu da uçtan uca analiz gecikmesinde neredeyse 3 kat artış sağladı. Sanal iş parçacıkları Önizleme özelliği olarak bilinen bu taşıma işleminin, performansın arttığı daha ileri bir tarih olabilir.

Dikkate alınması gereken bir diğer yaklaşım da, herhangi bir sorunla karşılaşmadan Loom eş yordamlarını beklemektir. kullanılabilir hale gelir. Bunun avantajı, proje kapsamının senkronizasyon ek yüklerinden kurtulmanıza yardımcı olur.

Diğer tüm yöntemler başarısız olursa bayt kodunun düşük düzeyde yeniden yazılması da uygun olabilir. alternatifidir. Yeterli optimizasyonla, hedeflerinize ulaşmak için performanslarına genel bir bakış sağlar.

Ek

Cehennem Geri Çağırma

Geri çağırma cehennemi, eşzamansız kodda geri çağırmaların kullanıldığı, çok bilinen bir sorundur. Bu, sonraki bir adımın devamının, iç içe yerleştirilmiş bir adımın kontrol edin. Birçok adım varsa bu iç içe yerleştirme işlemi son derece derin. Kontrol akışıyla birleştirilirse kod yönetilemez hale gelir.

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

İç içe yerleştirilmiş uygulamaların avantajlarından biri, dış adım korunabilir. Java'da, yakalanan lambda değişkenleri bu nedenle bu tür değişkenleri kullanmak zahmetli olabilir. Derin iç içe yerleştirme: gibi lambdas yerine devamlılık olarak yöntem referansları döndürülmesiyle aşağıdaki gibi gösterilir.

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 enjeksiyonu sırasında geri çağırma da gerçekleşebilir desen çok yoğun şekilde kullanılıyor, ancak araya giren enjeksiyonlarla bu durum önlenebilir emin olmanız gerekir.

Örnek: Zincirli SkyValue aramaları

Uygulama mantığı, çoğu zaman bağımlı zincirlere ihtiyaç duyar. SkyValue aramaları (örneğin, ikinci bir SkyKey ilk SkyValue'ya bağlıysa). Bunu çok nazik bir şekilde düşünerek, karmaşık bir veya geri çağırma yapısı vardır.

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;
}

Ancak devamlılıklar yöntem referansları olarak belirtildiğinden, kod durum geçişlerinde uygulanan prosedür: step2, step1 öğesini izler. Burada, lambda, value2 öğesini atamak için kullanılır. Bu, kodun sırasının yukarıdan aşağıya doğru sıralaması.

Çeşitli İpuçları

Okunabilirlik: Yürütme Sırası

Okunabilirliği iyileştirmek için StateMachine.step uygulamalarını korumaya çalışın ve geri çağırma uygulamalarında, bunların hemen gibi özellikler koda geçirilir. Kontrol akışı her zaman mümkün olmayabilir. dalları. Bu gibi durumlarda ek yorumlar faydalı olabilir.

Örnek: Zincirli SkyValue aramaları, bir Bunun için ara yöntem referansı oluşturulur. Bu, küçük bir performansı artırmaya yardımcı oluyor. Bu da muhtemelen burada buna değer.

Kuşak Hipotez

Orta ömürlü Java nesneleri Java'nın nesiller arasındaki hipotezini çökertebilir uzun süre yaşayan nesneleri işlemek için tasarlanmış veya sonsuza kadar kalan nesnelerdir. Tanımı gereği, SkyKeyComputeState bu hipotezi çiğnemiştir. Şunları içeren bu tür nesneler: kökleri Driver olan, hâlâ çalışan tüm StateMachine ağaçlarından oluşan bir ağaç askıya alınır ve eşzamansız hesaplamaları bekleyerek bir orta yaşam süresi tıklayın.

JDK19'da daha az kötü görünüyor ancak StateMachine kullanırken bazen görevlerde önemli düşüşler olsa bile, GC süresinde bir artış emin olun. StateMachineların orta yaşam ömrü olduğu için eski nesile yükseltilebilir ve böylece daha hızlı bir şekilde doldurulur. daha pahalı büyük veya tam GC'lerin temizlenmesine neden olabilir.

İlk önlem, StateMachine değişkenlerinin kullanımını en aza indirmektir ancak her zaman uygun değildir. Örneğin, birden fazla teklif stratejisi için bir değer eyaletler. Mümkün olduğunda, yerel yığın step değişkenleri genç nesildir ve verimli bir şekilde GC'ye dönüştürülmüştür.

StateMachine değişken için, işlemleri alt görevlere ayırın ve takip edin Bu örnekte, her bir satır öğesinde StateMachine olması da faydalıdır. Projenin gidişatı boyunca kalıp takip edildiğinde yalnızca alt StateMachine'lerin üst öğe referansı vardır StateMachine ve tersi değil. Bu, çocukların bir sonraki aşamaya geçip sonuç geri çağırmaları kullanarak ebeveynleri güncellediğinde, çocuklar da ve GC için uygun hale gelir.

Son olarak, bazı durumlarda, önceki eyaletlerde StateMachine değişkeni gerekir ancak sonraki eyaletlerde değil. Büyük boyutlu referansları geçersiz kılmak ancak gerekli olmadığı bilindiğinde bu nesneleri.

Adlandırma eyaletleri

Bir yöntemi adlandırırken, çalışma için genellikle bir yöntem belirtmek mümkündür bu sürecin kapsamına giriyor. Bunun nasıl yapılacağı da StateMachine var. Örneğin, foo yönteminin mevcut olduğunu varsayalım bir alt yöntemi bar çağırır. Bu, StateMachine dilinde şu dile çevrilebilir: foo durum dizisi ve ardından bar. foo artık bu davranışı içermiyor bar. Sonuç olarak, eyaletlere ilişkin yöntem adlarının kapsamı daha dar olma eğilimindedir. yerel davranışı yansıtabilir.

Eşzamanlılık ağacı diyagramı

Aşağıda, Yapılandırılmış eşzamanlılık yazın. Bloklar küçük bir ağaç oluşturur.

Yapılandırılmış Eşzamanlılık 3D


  1. Skyframe'in yeni baştan başlatma yaklaşımının aksine, değerleri kullanılamaz. 

  2. step adlı kişinin InterruptedException kullanmasına izin verildiğini, ancak örneklerde bu yoktur. Bazel kodunda, sonucu geçersiz kılan birkaç düşük yöntem vardır: ve Driver'a kadar yayılır (daha sonra açıklanacak şekilde, StateMachine çalıştıran bir komut dosyası oluşturun. Bu e-postanın silineceğini beyan etmemek, gereksiz.

  3. ConfiguredTargetFunction, eşzamanlı alt görevleri motive etti. her bağımlılık için bağımsız işler yürütür. Üzerinde değişiklik yapmak yerine tüm bağımlılıkları aynı anda işleyen karmaşık veri yapılarını, Verimsizlikler ortaya çıktığında, her bağımlılığın kendi bağımsız StateMachine

  4. Tek bir adımda birden fazla tasks.lookUp çağrısı birlikte gruplandırılır. Ek toplu işlemler, eşzamanlı olarak yapılan aramalarla oluşturulabilir. alt görevler olabilir. 

  5. Bu, kavram olarak Java'nın yapılandırılmış eşzamanlılığa benzer. jeps/428

  6. Bunu, bir ileti dizisi oluşturup çok daha fazlasıdır.