mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-07-04 02:01:33 +00:00
Particle lifecycle (#21)
Complete rethinking and refactoring of the codebase. The codebase basically consists these 5 moving parts now: 1. Fluence client (and the Particle processor which might be merged with the client) - This part is responsible for initiating Request flows, managing existing requests flows (it keeps the queue of received particles), pulling right strings on request flows to update their state etc 2. Fluence connection - This part is responsible for connecting to network, sending\receiving particles 3. RequestFlow - This is where the state of particle execution process is kept. It is basically a state storage with some control levers to update the state. Each request flow contains some particle lifecycle methods and the AquaCallHandler where all callback logic is kept 4. RequestFlowBuilder - This is where requests are prepared by the user (the developer of the client application) before they are ready to be sent into the network. 5. AquaCallHandler - This is how interpreter callbacks are handled. It is very similar to express.js app and is made of middlewares. Aqua handler is the unified api for both callbacks for our Request flows and non-ours (i.e services that are expected to be called be other peers). See `AquaHandler.ts` for details
This commit is contained in:
129
src/internal/RequestFlow.ts
Normal file
129
src/internal/RequestFlow.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import log, { trace } from 'loglevel';
|
||||
import PeerId from 'peer-id';
|
||||
import { AquamarineInterpreter } from './aqua/interpreter';
|
||||
import { AquaCallHandler } from './AquaHandler';
|
||||
import { InterpreterOutcome } from './commonTypes';
|
||||
import { FluenceConnection } from './FluenceConnection';
|
||||
import { Particle, genUUID } from './particle';
|
||||
|
||||
export const DEFAULT_TTL = 7000;
|
||||
|
||||
/**
|
||||
* The class represents the current view (and state) of distributed the particle execution process from client's point of view.
|
||||
* It stores the intermediate particles state during the process. RequestFlow is identified by the id of the particle that is executed during the flow.
|
||||
* Each RequestFlow contains a separate (unique to the current flow) AquaCallHandler where the handling of `call` AIR instruction takes place
|
||||
* Please note, that RequestFlow's is handler is combined with the handler from client before the execution occures.
|
||||
* After the combination middlewares from RequestFlow are executed before client handler's middlewares.
|
||||
*/
|
||||
export class RequestFlow {
|
||||
private state: Particle;
|
||||
private prevData: Uint8Array = Buffer.from([]);
|
||||
private onTimeoutHandlers = [];
|
||||
private onErrorHandlers = [];
|
||||
|
||||
readonly id: string;
|
||||
readonly isExternal: boolean;
|
||||
readonly script: string;
|
||||
readonly handler = new AquaCallHandler();
|
||||
|
||||
ttl: number = DEFAULT_TTL;
|
||||
|
||||
static createExternal(particle: Particle): RequestFlow {
|
||||
const res = new RequestFlow(true, particle.id, particle.script);
|
||||
res.ttl = particle.ttl;
|
||||
res.state = particle;
|
||||
setTimeout(res.raiseTimeout.bind(res), particle.ttl);
|
||||
return res;
|
||||
}
|
||||
|
||||
static createLocal(script: string, ttl?: number): RequestFlow {
|
||||
const res = new RequestFlow(false, genUUID(), script);
|
||||
res.ttl = ttl ?? DEFAULT_TTL;
|
||||
return res;
|
||||
}
|
||||
|
||||
constructor(isExternal: boolean, id: string, script: string) {
|
||||
this.isExternal = isExternal;
|
||||
this.id = id;
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
onTimeout(handler: () => void) {
|
||||
this.onTimeoutHandlers.push(handler);
|
||||
}
|
||||
|
||||
onError(handler: (error) => void) {
|
||||
this.onErrorHandlers.push(handler);
|
||||
}
|
||||
|
||||
async initState(peerId: PeerId): Promise<void> {
|
||||
const id = this.id;
|
||||
let currentTime = Date.now();
|
||||
|
||||
const particle: Particle = {
|
||||
id: id,
|
||||
init_peer_id: peerId.toB58String(),
|
||||
timestamp: currentTime,
|
||||
ttl: this.ttl,
|
||||
script: this.script,
|
||||
signature: '',
|
||||
data: Buffer.from([]),
|
||||
};
|
||||
|
||||
this.state = particle;
|
||||
setTimeout(this.raiseTimeout.bind(this), particle.ttl);
|
||||
}
|
||||
|
||||
receiveUpdate(particle: Particle) {
|
||||
// TODO:: keep the history of particle data mb?
|
||||
this.prevData = this.state.data;
|
||||
this.state.data = particle.data;
|
||||
}
|
||||
|
||||
async sendIntoConnection(connection: FluenceConnection): Promise<void> {
|
||||
const particle = this.state;
|
||||
try {
|
||||
await connection.sendParticle(particle);
|
||||
} catch (err) {
|
||||
log.error(`Error on sending particle with id ${particle.id}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
runInterpreter(interpreter: AquamarineInterpreter) {
|
||||
const interpreterOutcomeStr = interpreter.invoke(
|
||||
this.state.init_peer_id,
|
||||
this.state.script,
|
||||
this.prevData,
|
||||
this.state.data,
|
||||
);
|
||||
const interpreterOutcome: InterpreterOutcome = JSON.parse(interpreterOutcomeStr);
|
||||
// TODO:: keep the history of particle data mb?
|
||||
this.state.data = interpreterOutcome.data;
|
||||
return interpreterOutcome;
|
||||
}
|
||||
|
||||
getParticle = () => this.state;
|
||||
|
||||
hasExpired(): boolean {
|
||||
let now = Date.now();
|
||||
const particle = this.getParticle();
|
||||
let actualTtl = particle.timestamp + particle.ttl - now;
|
||||
return actualTtl <= 0;
|
||||
}
|
||||
|
||||
raiseError(error) {
|
||||
for (const h of this.onErrorHandlers) {
|
||||
h(error);
|
||||
}
|
||||
}
|
||||
|
||||
private raiseTimeout() {
|
||||
const now = Date.now();
|
||||
const particle = this.state;
|
||||
log.info(`Particle expired. Now: ${now}, ttl: ${particle?.ttl}, ts: ${particle?.timestamp}`);
|
||||
|
||||
for (const h of this.onTimeoutHandlers) {
|
||||
h();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user