-
Notifications
You must be signed in to change notification settings - Fork 5
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 cancellation to RPC handlers for the vaults
domain
#846
base: staging
Are you sure you want to change the base?
Add cancellation to RPC handlers for the vaults
domain
#846
Conversation
For each RPC handler, we need to provide a Should I make a commit into |
Don't bother defining the type for it unless you're actually using it. So you can just name the |
I just did this to mute the warnings for type hints, as TS assumes it is |
Please make sure to review the actual situation where this can cause a big problem: MatrixAI/Polykey-CLI#264 (comment). If you're fixing it, a test must be created to prevent regression of that specific problem. |
All this does is add support for cancelling RPC which are in the middle of something. This would ensure that a RPC won't keep the entire agent alive in a deadlock state. I'm not sure how this relates to adding support for streaming progress updates back. They can still be done just fine after this PR is implemented, no? |
a2baf5f
to
7b14e96
Compare
Polykey/src/client/handlers/VaultsClone.ts Lines 28 to 42 in 19e5278
This whole section of parsing const nodeId = ids.parseNodeId(input.nodeIdEncoded); This single line is more concise and performant than the |
isomorphic-git/isomorphic-git#1867
My idea is to use a deconstructed promise and reject the promise when the context sends an abort signal, but I'm not sure how this would affect other things, especially the underlying code in |
This requires fast check model checking applied. I don't trust it until sufficient variations of side effects are tested and the right defaults are found. Along with benchmarking. |
We check for abortion in multiple ways in Polykey. In some places, it is Which one should we follow consistently throughout the repo? |
await vaultManager.pullVault(
{
vaultId: vaultId,
pullNodeId: nodeId,
pullVaultNameOrId: pullVaultId,
tran: tran,
},
ctx,
); Here, the parameters are being sent as a JSON object for some reason. I want to know when a JSON object should be used for parameters and when should they be passed as-is. Before, the await vaultManager.pullVault({
vaultId: vaultId,
pullNodeId: nodeId,
pullVaultNameOrId: pullVaultId,
tran: tran,
}); The one benefit I see is that the parameters don't need to be ordered and can be set via setting key-value pairs like in Python. However, I can't do the same with the context, as it needs a special decorator which doesn't work well with JSON objects. @timedCancellable(true)
public async pullVault(
{
vaultId,
pullNodeId,
pullVaultNameOrId,
tran,
}: {
vaultId: VaultId;
pullNodeId?: NodeId;
pullVaultNameOrId?: VaultId | VaultName;
tran?: DBTransaction;
},
@context ctx: ContextTimed, // The decorator can't go in JSON object
): Promise<void>; How should I handle these cases, @tegefaulkes? |
I went over a couple of things with Brian in a meeting today, and this is the key takeaways.
|
I am kind of nitpicking here, but I noticed this minor point I want to bring up and get some clarification on. We rely on getting the class names for logger messages. This is done differently for static and instance methods for a class. In static methods, Can't we do something like a getter that returns the class name reliably in all contexts? class VaultInternal {
static get className(): string {
return this.name;
}
get className(): string {
return this.constructor.className;
}
// Now the class name can be obtained in any context using
// this.className
} Note that this code was generated by ChatGPT and has not been tested yet. This probably won't be the final implementation, but I am just asking if this idea can be implemented. |
We are also inconsistently using I think we should be explicit about this. Thoughts, @tegefaulkes? |
// This should really be an internal property
// get whether this is remote, and the remote address
// if it is, we consider this repo an "attached repo"
// this vault is a "mirrored" vault
if (
(await tran.get([
...this.vaultMetadataDbPath,
VaultInternal.remoteKey,
])) != null
) {
// Mirrored vaults are immutable
throw new vaultsErrors.ErrorVaultRemoteDefined();
} This was a comment made in |
Probably doesn't work that's why it's different. |
But is this a good thing to invest some time and effort in? This can probably be a quick fix as a part of this PR itself, or I can make an issue which explores this more in depth and maybe explores a way to get this in other repos without defining it in every class. |
Another option would be to repurpose the file to assist with other issues like #822. We shouldn't be keeping dead code unless there's a good reason. |
Lines 54 to 68 in 72c7dfb
The type for |
Did you review this: https://github.com/MatrixAI/js-rpc?tab=readme-ov-file#specifications? |
No it is not equivalent. There are certain exceptions we expect from the validator and matcher. |
Yes so refer to my comment here: #846 (comment). Any progress update system SHOULD not be part of the regular RPC call, that over complicates our API and makes it harder to integrate. Remember even a unary call can trigger a long running operation on the agent side (and downstream effects magnify this due to "Call Amplification"). All progress updates depend on the measurement point. A client may measure between calls it makes by chunking up its calls. An example is like MULTIPLE RPC call or a client stream that streams chunks at a time expecting a synchronous response. This doesn't require the server side to necessarily have a separate protocol to give progress updates, it's implicit to the algorithm which makes it elegant. On the other hand, let's say you want to "break up" a long running async operation to get progress updates, rather than building a separate protocol for that, you could try to just do chunking of the request. On the other hand, in some cases it really doesn't make sense to chunk the request, since the request is spatially/structurally small on the client side, it just takes a long time on the server side. You don't necessarily want to change your unary call into a server streaming call cause that changes the API and makes it needlessly complex for users who just want to do a regular call. So in those scenarios, you introduce out-of-band RPC calls to get this information. This is where #444 comes into play. We can make use of a generalized push outlet, and stream information from there, that allows clients at their discretion to get extra progress information from a separate system. Another way is to introduce a streaming variant of the same call, this enables clients again to use a more complex API if they want the progress update version of the same call. This is common in functional programming where one creates |
So isogit is a third party library. We often come across third party library problems, if it is structurally a problem, we fork them and implement it ourselves - we can do so by creating js-virtualgit. However I remember that in the case of isogit, we actually hacked into the HTTP stream itself, where the HTTP stream is virtualised ontop of our RPC stream protocol. That would mean, you could implement a sort of abortion at the raw RPC stream level, by aborting it at the stream. This is because when we use isogit's features, we are actually wrapping its entire IO, so by being able to control the IO we can inject an abortion at the stream rather than relying on the internal mechanism of isogit to do so. This is absolutely possible but you need to diagram out a block diagram of the wrapped structure to demonstrate. |
3ea2dc9
to
cf4426f
Compare
While trying to implement cancellation checks, I tried to do the following. This, however, does not work. I get the following error. test('should fail when cancelled', async () => {
const response = await rpcClient.methods.vaultsSecretsRemove();
response.cancel('reason');
const consumeP = async () => {
for await (const _ of response.readable);
};
await expect(consumeP()).rejects.toThrow('reason');
});
Then I started investigating this by going in deeper into I then printed out the contents of
Then, after this, another console.log statement is returned. This happens due to the post-test hook closing the websocket client. I also put console.log statements throughout the actual
After going through the entire object, I can confirm that |
It looks like the this context might be lost. You may need to find where to do an explicit binding. |
@tegefaulkes have you reviewed this? |
BTW make sure you don't leave any vestigial comments like TODOs. |
There are some observations that I have made. We are not consistent about parameter order. In some places After a discussion with Brian, this is what we want to do - keep the callback as the last parameter. However, our code uses both the approaches in a mixed manner. Should we be concerned about it, or leave it as-is? A change would involve a large amount of minor modifications of the codebase for both Polykey and Polykey CLI. What do you think, @CMCDragonkai? |
This PR is finally ready for merging. @tegefaulkes, can you please review this PR once again, as a big amount of changes were made after your previous review. |
You should write a spec in graph.matrix.ai. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small changes
cfe3556
to
8053380
Compare
All the review points from @tegefaulkes have been addressed. Once he has approved changes in both Polykey and Polykey CLI, the merge can happen in both places at the same time. |
Description
Without proper abortion or cancellation for a RPC, it will keep running and conclude first. This ends up adding a significant amount of time when closing the agent, as all outstanding RPC calls must resolve beforehand.
Issues Fixed
Tasks
VaultInternal.ts
VaultManager.ts
VaultsVersion
VaultsClone
VaultsPull
VaultsCreate
VaultsDelete
VaultsList
VaultsLog
VaultsRename
VaultsScan
VaultsPermissionsGet
VaultsPermissionsSet
VaultsSecretsCat
VaultsSecretsEnv
VaultsSecretsGet
VaultsSecretsList
VaultsSecretsMkdir
VaultsSecretsNew
VaultsSecretsNewDir
VaultsSecretsRemove
VaultsSecretsRename
VaultsSecretsStat
VaultsSecretsWriteFile
SuccessOrErrorMessage
tag in Polykey CLIOld tasks list
AgentLockAll
uncancellable unary commandAgentStatus
uncancellable unary commandAgentStop
cant cancel unary noop commandAgentUnlock
AuditEventsGet
AuditMetricGet
GestaltsActionsGetByIdentity
GestaltsActionsGetByNode
GestaltsActionsSetByIdentity
GestaltsActionsSetByNode
GestaltsActionsUnsetByIdentity
GestaltsActionsUnsetByNode
GestaltsDiscoveryByIdentity
GestaltsDiscoveryByNode
GestaltsDiscoveryQueue
GestaltsGestaltsGetByIdentity
GestaltsGestaltsGetByNode
GestaltsGestaltsList
GestaltsGestaltsTrustByIdentity
GestaltsGestaltsTrustByNode
IdentitiesAuthenticate
IdentitiesAuthenticatedGet
IdentitiesClaim
IdentitiesInfoConnectedGet
IdentitiesInfoGet
IdentitiesInvite
IdentitiesProvidersList
IdentitiesTokenDelete
IdentitiesTokenGet
IdentitiesTokenPut
KeysCertsChainGet
KeysCertsGet
KeysDecrypt
KeysEncrypt
KeysKeyPair
KeysKeyPairRenew
KeysKeyPairReset
KeysPasswordChange
KeysPublicKey
KeysSign
KeysVerify
NodesAdd
NodesClaim
NodesFind
NodesGetAll
NodesListConnections
NodesPing
NotificationsInboxClear
NotificationsInboxRead
NotificationsInboxRemove
NotificationsOutboxClear
NotificationsOutboxClear
NotificationsOutboxRead
NotificationsOutboxRemove
NotificationsSend
VaultsClone
VaultsCreate
VaultsDelete
VaultsList
VaultsLog
VaultsPermissionGet
VaultsPermissionSet
VaultsPermissionUnset
VaultsPull
VaultsRename
VaultsScan
VaultsSecretsCat
VaultsSecretsEnv
VaultsSecretsGet
VautlsSecretsList
VaultsSecretsMkdir
VaultsSecretsNew
VaultsSecretsNewDir
VaultsSecretsRemove
VaultsSecretsRename
VaultsSecretsStat
VaultsSecretsWriteFile
VaultsVersion
NodesClaimNetworkSign
NodesClaimNetworkVerify
NodesClaimsGet
NodesClosestActiveConnectionsGet
NodesClosestLocalNodesGet
NodesConnectionSignalFinal
NodesConnectionSignalInitial
NodesCrossSignClaim
NotificationsSend
VaultsGitInfoGet
VaultsGitPackGet
VaultsScan
Final checklist