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

Add optional dispose flag for forks after transaction #23298

Merged
merged 6 commits into from
Dec 19, 2024

Conversation

daesunp
Copy link
Contributor

@daesunp daesunp commented Dec 11, 2024

Description

This PR adds a flag to not dispose of the fork after the transaction is commited/aborted.

@github-actions github-actions bot added area: dds Issues related to distributed data structures area: dds: tree base: main PRs targeted against main branch labels Dec 11, 2024
@@ -482,6 +483,9 @@ export type SharedTreeOptions = Partial<ICodecOptions> &
Partial<SharedTreeFormatOptions> &
ForestOptions;

export interface SharedTreeOptionsSpecial extends SharedTreeOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about "SharedTreeOptionsInternal"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to suggested name

@@ -393,6 +395,7 @@ export class TreeCheckout implements ITreeCheckoutFork {
/** Optional logger for telemetry. */
private readonly logger?: ITelemetryLoggerExt,
private readonly breaker: Breakable = new Breakable("TreeCheckout"),
private readonly disposeForksAfterTransaction?: boolean,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private readonly disposeForksAfterTransaction?: boolean,
private readonly disposeForksAfterTransaction = false,

If you default on initialization, then it will be a boolean rather than a boolean | undefined, and any code in the class that reads it doesn't need to worry about handling undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to default on initialization


forks.forEach((fork) => fork.dispose());
if (this.disposeForksAfterTransaction !== false) {
forks.forEach((fork) => fork.dispose());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're not doing this disposal, you shouldn't need any of the fork tracking code above either. The only reason we subscribe to the fork event is to do this disposal. If disposeForksAfterTransaction is false, then probably just have forks be undefined or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to only track forks when dispose flag is true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still some code running that doesn't need to. How about this: move all of the code related to the forking stuff into a nice, encapulated helper function. Something like:

// Keeps track of all new forks created until the returned function is invoked, which will dispose all of those forks
// The returned function may only be called once
function trackForksForDisposal(checkout: TreeCheckout): () => void {
    const forks = new Set<TreeCheckout>();
    let onForkUnSubscribe: () => void | undefined;
    const onDisposeUnSubscribes: (() => void)[] = [];
    onForkUnSubscribe = onForkTransitive(checkout, (fork) => {
        forks.add(fork);
        onDisposeUnSubscribes.push(fork.events.on("dispose", () => forks.delete(fork)));
    });
    let disposed = false;
    return () => {
        assert(!disposed, "Forks may only be disposed once");
        forks.forEach((fork) => fork.dispose());
        onDisposeUnSubscribes.forEach((unsubscribe) => unsubscribe());
        onForkUnSubscribe();
        disposed = true;
    }
}

Then you can just do:

const disposeForks = this.disposeForksAfterTransaction ? collectForksForDisposal(this) : undefined;
// ...
disposeForks?.();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, it does not seem to populate the forks properly when combining everything into one function. I have created 2 helper functions to simplify it a bit though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. Why? It should be a pretty straightforward code move 🤔

The way it is now is more complicated and verbose than either the original state or the above suggestion, IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake! Turns out I was calling trackForksForDisposal in the wrong place (within the return of the constructor). It seems to be working properly :)

@daesunp daesunp marked this pull request as ready for review December 18, 2024 19:33
@daesunp daesunp requested a review from a team as a code owner December 18, 2024 19:33

forks.forEach((fork) => fork.dispose());
if (this.disposeForksAfterTransaction !== false) {
forks.forEach((fork) => fork.dispose());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still some code running that doesn't need to. How about this: move all of the code related to the forking stuff into a nice, encapulated helper function. Something like:

// Keeps track of all new forks created until the returned function is invoked, which will dispose all of those forks
// The returned function may only be called once
function trackForksForDisposal(checkout: TreeCheckout): () => void {
    const forks = new Set<TreeCheckout>();
    let onForkUnSubscribe: () => void | undefined;
    const onDisposeUnSubscribes: (() => void)[] = [];
    onForkUnSubscribe = onForkTransitive(checkout, (fork) => {
        forks.add(fork);
        onDisposeUnSubscribes.push(fork.events.on("dispose", () => forks.delete(fork)));
    });
    let disposed = false;
    return () => {
        assert(!disposed, "Forks may only be disposed once");
        forks.forEach((fork) => fork.dispose());
        onDisposeUnSubscribes.forEach((unsubscribe) => unsubscribe());
        onForkUnSubscribe();
        disposed = true;
    }
}

Then you can just do:

const disposeForks = this.disposeForksAfterTransaction ? collectForksForDisposal(this) : undefined;
// ...
disposeForks?.();

) {
super({ jsonValidator: typeboxValidator });
super(options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this better?

Suggested change
super(options);
super({ ...options, jsonValidator: typeboxValidator });

You want this to use typeboxValidator no matter what, correct? Otherwise it's broken? Or, is there a legitimate use case for a subclass of this which allows a different validator?

And if so, you'd still want to supply typeboxValidator if the subclass didn't supply anything. So that'd at least be:

super({ ...options, jsonValidator: options.jsonValidator ?? typeboxValidator })

In either case, you can change the default value for the options parameter to be {}, and you should follow the same pattern in the other constructor of your new subclass (because it could be subclassed).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated with suggestion

Copy link
Contributor

@noencke noencke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@@ -913,3 +909,22 @@ export class TreeCheckout implements ITreeCheckoutFork {
);
}
}

// Keeps track of all new forks created until the returned function is invoked, which will dispose all of those forks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: one of the benefits of factoring code out into helper functions is that you can put doc-comments on them. So let's take advantage of that and make this a full-on doc comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added doc comment to helper function

@daesunp daesunp merged commit 83c318f into microsoft:main Dec 19, 2024
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: dds: tree area: dds Issues related to distributed data structures base: main PRs targeted against main branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants