總覽
SkyFrame StateMachine
是「已拆解」的函式物件,位於
堆積。可在無須備援的情況下享有彈性和評估能力1。
您無法立即取得必要的值,不過是以非同步方式計算。
StateMachine
在等待期間無法連結執行緒資源,但必須
政策就會暫停並重新啟用解構函式可能會
以便略過先前的運算
StateMachine
可用來表示序列、分支版本、結構化邏輯
而且專為 SkyFrame 互動而設計。
StateMachine
可以組成大型 StateMachine
,並與他人分享
子 StateMachine
。並行的結構一律是結構體,
完全合乎邏輯每個並行子工作都是在單一共用父項中執行
SkyFunction 執行緒。
簡介
本節會短暫提起並介紹 StateMachine
,
java.com.google.devtools.build.skyframe.state
套件。
SkyFrame 重新啟動的簡短說明
SkyFrame 是對依附元件圖表執行平行評估的架構。
圖表中的每個節點都對應了具有
SkyKey 指定其參數,以及指定其結果的 SkyValue。
運算模型是,SkyFunction 可以根據 SkyKey 查詢 SkyValues,
觸發其他 SkyFunction 的週期性且平行評估作業。而不是
封鎖,在要求 SkyValue 尚未包含
預備工作,因為部分運算子圖不完整
SkyFunction 會觀察 null
getValue
回應,且應傳回 null
而不是 SkyValue,表示由於缺少輸入內容而不完整。
SkyFrame 會在所有先前要求過的 SkyValues 時重新啟動 SkyFunction
可供使用。
在 SkyKeyComputeState
導入之前,傳統的處理方法
則是重新執行完整運算雖然這會是二次
以這種方式編寫的函式最終會完成
但會傳回 null
的查詢。有了 SkyKeyComputeState
,就能
將手動指定的查核點資料與 SkyFunction 建立關聯,這樣就能省下大量成本
會出現 BERT 事件
StateMachine
是存在 SkyKeyComputeState
且消除中的物件
在 SkyFunction 重新啟動時幾乎所有重新計算 (假設
SkyKeyComputeState
不會因存取暫停並繼續作業而從快取中消失
執行掛鉤
SkyKeyComputeState
內的有狀態運算
從物件導向的設計的角度來看,因此最好考慮儲存
SkyKeyComputeState
內的運算物件,而不是純資料值。
在 Java 中,攜帶物件的行為最小描述為
功能介面,而這一切都滿足了。StateMachine
下文、充滿好奇心且定義如下2。
@FunctionalInterface
public interface StateMachine {
StateMachine step(Tasks tasks) throws InterruptedException;
}
Tasks
介面與 SkyFunction.Environment
類似,但
專為非同步作業設計,並新增支援邏輯並行子工作3。
step
的傳回值是另一個 StateMachine
,能夠指定規格
一致。step
會在DONE
StateMachine
已完成。例如:
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
步驟,而非
單體函式,提供暫停暫停和恢復
運算能力StateMachine.step
傳回時,系統會明確暫停訂閱
點。回傳的 StateMachine
值指定的接續值為
明確的 resume 點。因此可以避免重新計算
便能從中斷的地方接續進行。
回呼、接續與非同步運算
在技術術語中,StateMachine
做為「接續」,
來執行StateMachine
可以不必封鎖,而是改為
使用者從 step
函式 (傳輸
改回 Driver
執行個體。Driver
可以
然後切換至準備就緒的 StateMachine
,或恢復使用 SkyFrame。
傳統上,「回呼」和「接續」會形成一個概念。
不過,StateMachine
會保留兩者。
- 回呼 - 說明要儲存非同步結果結果的位置 運算能力
- 接續 - 指定下一個執行狀態。
叫用非同步作業時需要回呼, 實際作業不會在呼叫 方法後立即發生,如 也就是 SkyValue 查詢的情況回呼應盡可能保持簡單。
接續是 StateMachine
和 StateMachine
的傳回值。
封裝所有非同步執行
運算作業解析這種結構化做法
可管理回呼。
工作
Tasks
介面提供 StateMachine
以及可查詢 SkyValues 的 API
以及排定並行子工作
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 查詢
StateMachine
會使用 Tasks.lookUp
超載查詢 SkyValues。這些
類似 SkyFunction.Environment.getValue
和
SkyFunction.Environment.getValueOrThrow
和類似的例外狀況處理功能
語意實作不會立即執行查詢,而
而是在執行這項作業前,盡可能批次處理4查詢次數。這個鍵
可能無法立即使用,例如需要重新啟動 SkyFrame
因此,呼叫端會使用回呼指定如何處理結果值。
StateMachine
處理器 (Driver
,且橋接到
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
的查詢結果已完成。因此,value
在下列情況下可供使用:
您可在 processValue
中存取。
子工作
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並行,如下所示
範例。
從 UML 可得知並行結構形成了樹狀結構。 有替代檢視模式能更有效地顯示 樹狀結構的名稱
結構化並行就是容易理解的。
組合和控制流程模式
本節舉例說明如何組合多個 StateMachine
特定控制流程問題的相關解決方案
依序狀態
這是最常見也最直接的控制流程模式。評估
如此便可使用
SkyKeyComputeState
。
分支版本
StateMachine
中的分支狀態可透過傳回不同的
值,如以下範例所示。
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
、S 的 StateMachine
執行個體。
其中 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
呼叫,必須在 S 之前完成
執行作業。在此情況下,將 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
的巢狀結構就是
Callback Hell 的道路。建議依循循序漸進的方式
具有一般序列狀態的 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
及早發生錯誤事實上,錯誤通常會因為錯誤而
由包含 runAfter
參照的 StateMachine
執行兩次
由 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
回呼
查詢。本節提供原因和建議
處理多個 SkyValue 的方法。
Tasks.lookUp
回呼
Tasks.lookUp
方法會將回呼 sink
做為參數。
void lookUp(SkyKey key, Consumer<SkyValue> sink);
慣用的方法是使用 Java lambda 來實作:
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
類別
實作回呼可節省 lambda 的記憶體配置。
「錯誤處理」可提供更多細節,但基本上, 錯誤和正常值的傳播卻沒有太大的差異。
使用多個 SkyValue
通常需要多次查詢 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>
回呼實作可以明確共用
因為值類型不同如果事實並非如此
以 lambda 為基礎的實作,或實作
可用的回呼。
在 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
的替代方案,請參閱 Driver
和
連結至 SkyFunctions
處理錯誤
Tasks.lookUp
已有一些錯誤處理的例子:
回呼和在
StateMachines
。除了
InterruptedException
不會擲回,但會傳遞到
回呼做為值。這類回呼通常具有專屬或語意,且
只傳回其中一個值或錯誤。
下一節將介紹一些細微但重要的互動,並與 SkyFrame 互動 錯誤處理機制
錯誤啟動 (--nokeep_going)
在錯誤啟動期間,即使未要求所有要求,SkyFunction 仍可能會重新啟動
可使用 SkyValues。在這種情況下,後續狀態一律不會
已因 Tasks
API 合約而達成。不過,StateMachine
應
仍會傳播例外狀況
因為無論達到下一個狀態,傳播作業都必須進行
錯誤處理回呼必須執行這項工作。對於內部 StateMachine
:
方法是叫用父項回呼。
在頂層 StateMachine
(與 SkyFunction 的介面) 中,這可
方法是呼叫 ValueOrExceptionProducer
的 setException
方法。
ValueOrExceptionProducer.tryProduceValue
也會擲回例外狀況,即使
缺少 SkyValues。
如果是直接使用 Driver
,請務必檢查
即使機器尚未完成,仍可從 SkyFunction 傳播錯誤
和資料處理之間
事件處理
針對需要發出事件的 SkyFunctions,系統會插入 StoredEventHandler
並進一步插入需要用到 SkyKeyComputeState 的 StateMachine
具體做法是指示 Kubernetes 建立並維護
一或多個代表這些 Pod 的物件過去由於 SkyFrame 下降,因此需要使用 StoredEventHandler
的特定事件,除非這些事件再次發生,不過之後又修正了。
保留 StoredEventHandler
插入,因為這麼做簡化了
處理錯誤處理回呼所產生的事件。
Driver
並橋接至 SkyFunctions
Driver
負責管理 StateMachine
的執行作業。
開頭為指定的根 StateMachine
。因為 StateMachine
可以
子工作 StateMachine
會以遞迴方式排入佇列,單一 Driver
可以管理
許多子工作這些子工作會建立樹狀結構,結果是
結構化並行。Driver
會批次處理 SkyValue
查詢所有子工作的查詢,進而提升效率。
許多使用 Driver
建構的類別,且使用下列 API。
public final class Driver {
public Driver(StateMachine root);
public boolean drive(SkyFunction.Environment env) throws InterruptedException;
}
Driver
會將單一根層級 StateMachine
做為參數。撥號中
Driver.drive
會盡可能執行 StateMachine
,不使用
重新啟動 SkyFrame。如果 StateMachine
完成且傳回 false,則會傳回 true
否則代表無法使用所有值。
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.
}
SkyFunction 的程式碼可能如下所示 (其中 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
類別具有同步 API 以供比對
同步的 SkyFunction 程式碼
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
執行個體,類似於
嵌入驅動程式和介面中的 ResultProducer
類別
以類似的方式使用 SkyFunction與其定義 ResultSink
發生其中一種情形時,系統會呼叫 setValue
或 setException
。
當這兩種情況發生時,以例外狀況為優先。tryProduceValue
方法
將非同步回呼程式碼橋接至同步程式碼,並擲回
而非例外狀況
如先前所述,在錯誤泡泡期間,可能發生錯誤
即使是機器也可能尚未完成
因為並非所有輸入都能取得目的地:
因此,tryProduceValue
會擲回任何已設定的例外狀況,即使
都沒問題
Epilogue:最終移除回呼
StateMachine
是效率極高,但需要大量樣化的樣板來執行
非同步運算接續 (尤其是 Runnable
的形式)
傳遞至 ListenableFuture
) 在 Bazel 程式碼的某些部分廣泛,
但對於 SkyFunctions 的分析而言較不重要分析主要取決於 CPU 限制
沒有高效率的非同步 API 用於磁碟 I/O。最終
最好將回呼最佳化,因為回呼會經過學習,且會阻礙
且可讀性高
Java 虛擬執行緒是最具潛力的替代方案之一。而不是
不必編寫回呼,所有內容都會替換為同步、封鎖
呼叫。這可能是因為此資源連結虛擬執行緒資源,與
所謂平台執行緒,就應節省成本。但即使有了虛擬執行緒
以建立執行緒和同步處理作業取代簡易的同步作業
基元太貴了我們已將 StateMachine
秒的資料遷移至
Java 虛擬執行緒和數量級的處理速度較慢,
端對端分析的延遲時間幾乎是 3 倍。因為虛擬執行緒
這項遷移作業可在某個位置執行
等到績效改善時再新增日期。
另一個考量方式是等待「Loom」協同程式 (如果有的話) 可供使用。這種做法的好處是或許可以減少 以便進行多工處理。
如果其他方法都故障,低階位元碼重寫也可以是可行的做法 。若最佳化程度夠高,或許也能 改用手動編寫回呼程式碼的效能。
附錄
回歸地獄
回呼堆積是使用回呼的非同步程式碼中惡名昭彰的問題。 原因在於後續步驟的接續步驟為巢狀結構 即可。如果有多個步驟 深度。如果加上控制流程,程式碼就無法管理。
class CallbackHell implements StateMachine {
@Override
public StateMachine step(Tasks task) {
doA();
return (t, l) -> {
doB();
return (t1, l2) -> {
doC();
return DONE;
};
};
}
}
巢狀結構的一個優點是, 外部步驟可保留下來。在 Java 中,擷取的 lambda 變數必須是 最終,這樣的變數使用起來可能很麻煩。深層巢狀結構是 會避免透過傳回方法參照做為接續的結果,而非 lambda 如下所示。
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 查詢
大多數情況下,應用程式邏輯需要依附元件鏈結 例如,如果第二個 SkyKey 依賴第一個 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
。請注意,這裡的
lambda 的用途是指派 value2
。如此一來,程式碼的順序與
計算順序。
其他訣竅
可讀性:執行作業排序
為提升可讀性,請努力保留 StateMachine.step
實作項目
依照執行順序和回呼實作。
。因此控制流程並非面面俱到
如果是這種情況,其他意見可能有所幫助。
在範例:鏈結 SkyValue 查詢中, 為達成此目標,我們建立了中繼方法參考。這意味著 但值得注意的是
生成假設
中型 Java 物件會破壞 Java 的生成假設
垃圾收集器專為處理
只能長短時間,或是永遠存在根據定義
SkyKeyComputeState
違反這個假設。這類物件,包含
所有仍在執行中的 StateMachine
(根層級為 Driver
) 的建構樹狀結構,已
因其暫停而暫停的中階生命週期,等待非同步運算
以完成。
在 JDK19 中似乎不太糟,但使用 StateMachine
時,有時可能會
可以觀察到 GC 時間的增加
實際產生的垃圾資訊因為 StateMachine
的生命週期中位數
只要將模型升級為舊版,就能更快裝滿
並不需要用到更昂貴的主要或完整的 GC 清理。
初期預防措施是盡量減少使用 StateMachine
變數,但
並非每次都可行,例如在多個多個
州。如果可以,本機堆疊 step
變數為年輕世代
GC 的程式碼
如果是 StateMachine
變數,請將項目分成多個子工作,並遵循
將值傳播於
StateMachine
s 也提供相關資訊。請留意
遵循這個模式,只有子項 StateMachine
參照了父項
StateMachine
,反之亦然。也就是說,隨著孩子
使用結果回呼來更新父項,而子項自然地退出
且符合使用 GC 的資格
最後,在某些情況下,先前的狀態需要使用 StateMachine
變數
但之後狀態不會出現對大型大型檔案的參照來說,可能很有利
已知不再需要的物件
命名狀態
為方法命名時,通常可以為行為命名方法
發生什麼情況實作起來不太清楚
StateMachine
,因為沒有堆疊。例如,假設 foo
方法
會呼叫子方法 bar
。在 StateMachine
中,這個值可轉譯為
狀態序列 foo
,後面接著 bar
。foo
不再包含行為
bar
。因此,狀態的方法名稱通常範圍較小
可能反映出當地行為
並行樹狀圖
以下是「結構化圖片」中圖表的替代檢視模式 以更合適的方式呈現樹狀結構的結構。 方塊會形成一棵小樹。
-
SkyFrame 自從如何從頭開始 但無法使用這些值。 ↩
-
請注意,
step
可以擲回InterruptedException
,但 範例會省略此項。Bazel 程式碼中有幾個低的方法 並套用至Driver
,稍後將說明 執行StateMachine
的指令您可以在 無謂↩ -
同時執行的子工作是由「
ConfiguredTargetFunction
」提供,: 會為每個依附元件執行「獨立」工作。與其進行遷移 處理所有依附元件的複雜資料結構 如果效率不彰,每個依附元件都有各自的StateMachine
。↩ -
單一步驟中的多個
tasks.lookUp
呼叫會批次處理。 並行查詢則可建立額外的批次 子工作。 ↩ -
這與產生執行緒並彙整,藉此達到目的 序列組成 ↩