Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API] usingMachineRunner to borrow a machine (as opposed to owning) #62

Open
Kelerchian opened this issue Aug 4, 2023 · 1 comment
Open
Assignees

Comments

@Kelerchian
Copy link
Contributor

Kelerchian commented Aug 4, 2023

Problem

A MachineRunner instance contains a living Actyx subscription before it is destroyed. Currently, the programmer has to make sure that either for-await loop is used or destroy to make sure an orphan subscription is not running. There are a lot of cases where an instance is used temporarily without a 'for-await' loop; it is created, extracted, and then destroyed. Such cases have a risk---a missed destroy call.

Missing a destroy call is caused by an early return or a throw. But its root cause is the lifetime ownership of MachineRunner. For this issue, I am going to borrow some terms:

  • Lifetime of a MachineRunner: The period when a MachineRunner lives and its destruction
  • Ownership of a MachineRunner; The responsibility of a function or an object over a MachineRunner's lifetime --- the need to explicitly call 'destroy' after an instance is not used anymore

For example, an early return creates an orphaned machine.

const extractSomeData = async (): Promise<SomePayload | null> => {
  const machine = createMachineRunner(actyx, tags, Initial, payload); // a machine is created
  const state = (await machine?.peek())?.value;
  // An early return. Here, the program forgets to call `machine.destroy()`, thus creating a living orphan machine
  if (!state) {
    return null;
  }
  const payload = state.payload;
  machine.destroy();
  return payload;
}

A manual review is required to see the mistake in the piece of code above. The proper fix is:

if (!state) {
  machine.destroy();
  return null;
}

This is only one example of a missable destroy call. Another class of missable is less visible to a code review, which is a thrown Exception.

Solution

The proposed API aims to take over the ownership of a MachineRunner and instead allow the programmer to borrow an instance.

The API set will expose two things:

  • a function usingMachineRunner which accepts a similar set of arguments to createMachineRunner.
  • a type MachineRunnerUseFn<Factory, ReturnType, Payload = unknown> which enables the user to write a 'dependency-injection' style.

With this API, the problematic example above can be rewritten as:

const extractSomeData = () => usingMachineRunner(actyx, tags, Initial, Payload, async (machine) => {
  const state = (await machine?.peek())?.value;
  if (!state) return null;
  const payload = state.payload;
  return payload;
});

usingMachineRunner does what createMachineRunner does and then execute the function passed by the user. With this pattern, the library now can destroy the machine after the function execution is done or if it throws, therefore eliminating the need for an explicit call to the destroy method.

for-await loop can also be used in the same way.

usingMachineRunner(actyx, tags, Initial, Payload, async (machine) => {
  for await (const state of machine) {
    ...
  }
});

Dependency injection can be achieved by the provided type. For example, a class wants to allow parametrization to only tags and the function body.

import { Initial } from "protocol/someRole";
class SomeClass {
  actyx: Actyx,
  
  // Dependency injection happens here
  useMachine<T>(tags: Tags, useFn: MachineRunnerUseFn<Initial, T>){
    return usingMachineRunner(this.actyx, tags, Initial, useFn);
  }
}

// subsequently the class can be used this way
const someClass = new SomeClass(...);
const result = await someClass.useMachine(createSomeTags(), (machine) => {
  for await (const state of machine) {
    ...
  }
});
@Kelerchian Kelerchian changed the title usingMachineRunner [API] usingMachineRunner to borrow instead of own a MachineRunner Aug 4, 2023
@Kelerchian Kelerchian changed the title [API] usingMachineRunner to borrow instead of own a MachineRunner [API] usingMachineRunner borrowing a machine (as opposed to owning) Aug 10, 2023
@Kelerchian Kelerchian changed the title [API] usingMachineRunner borrowing a machine (as opposed to owning) [API] usingMachineRunner to borrow a machine (as opposed to owning) Aug 10, 2023
@rkuhn
Copy link
Member

rkuhn commented Sep 6, 2023

Yes, this is a good idea — in particular in light of seeing machine leaks in production code.

@Kelerchian Kelerchian self-assigned this Feb 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

2 participants