-
-
Notifications
You must be signed in to change notification settings - Fork 163
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
Using the types TransferDescriptor in typescript #348
Comments
Hey @CMCDragonkai, thanks for reporting! I do think it's an issue with the types. Workers can return transferables and the main thread will see no difference. The code normalizing the worker's result can be found here. Now the worker function type defs in Give me a little bit of time to prepare a PR :) |
Hey @andywer I got around to trying this again and it still doesn't work. This is the function I have inside my worker:
Am I supposed to have I'm not able to use |
Do you have an example that uses Node buffers, and not ArrayBuffer? |
This is how I call it:
There is still a type error on
It seems that either there's some corruption sending a Node buffer's array buffer over and there's also a type error, in that |
Ok I think I understood what happened. Node buffers use a large ArrayBuffer. To properly use them I have to also transfer the offset and length information. However there is in fact another type error. This happens when there are multiple Typescript complains that using For example:
That's the type inferred in vscode. See how the second parameter is just Now it is possible for me to specify the array buffer variable without using For now I have to use |
Ok I have got it working now. Is this right way to do all of this:
And
I'm concerned about the part where I have to use Furthermore the comments say that, if I use |
Hey @CMCDragonkai! Good to see you figured most of it out already. Yes, so the way to invoke functions that use transferable objects is very closely related to the way you would use transferable objects without threads.js – mainly for performance reasons. There are still a few misconceptions, I think, and the code can also be improved. How about this (haven't tried to run it, but it should convey the idea): encryptWithKey(
key: {
data: ArrayBuffer,
offset: number,
length: number,
},
plainText: {
data: ArrayBuffer,
offset: number,
length: number,
}
): TransferDescriptor<[ArrayBuffer, number, number]> and cipherText = await workerManager.call(
async w => {
const [arrayBuf, arrayBufOffset, arrayBufLength]= await w.encryptWithKey(
Transfer({ data: key.buffer, offset: key.byteOffset, length: key.byteLength }, key.buffer),
Transfer({ data: plainText.buffer, offset: plainText.byteOffset, length: plainText.byteLength }, plainText.buffer),
);
return Buffer.from(arrayBuf, arrayBufOffset, arrayBufLength);
}
); You could then simplify it further by not crafting a new kind of object that resembles the node.js Buffer, but actually passing the Buffer. To make that work you will probably need to write a serializer/deserializer for import { registerSerializer, SerializerImplementation } from "threads"
interface SerializedBuffer {
__type: "$$Buffer"
buffer: ArrayBuffer
byteOffset: number
byteLength: number
}
const BufferSerializer: SerializerImplementation = {
deserialize(thing, defaultHandler) {
if (thing && thing.__type === "$$Buffer") {
return Buffer.from((thing as any).buffer, (thing as any).byteOffset, (thing as any).byteLength)
} else {
return defaultHandler(thing)
}
},
serialize(thing, defaultHandler) {
if (thing instanceof Buffer) {
return {
__type: "$$Buffer",
buffer: thing.buffer,
byteOffset: thing.byteOffset,
byteLength: thing.byteLength
}
} else {
return defaultHandler(thing)
}
}
}
registerSerializer(BufferSerializer) See https://threads.js.org/usage-advanced#custom-message-serializers for details. I have to admit, the documentation is not so easy to understand for these advanced features… |
@andywer thanks for the advice. However I'm curious as to how to deal with the detachment of the array buffer. If the array buffer from the main thread is detached, how can I re-use the key and plaintext buffers for a subsequent operation on threadsjs? Furthermore the Node Buffer's ArrayBuffer did not get detached, so I found that it was still a copy. |
That's why it's called transferable objects: You transfer the data from one thread to another instead of copying it. Of course you cannot use it in the source thread anymore, unless the second thread transfers it back after it's done. Your use case sounds as if you really aim to be able to use the keys in both threads independently. If that's the case then you might actually want to not transfer the data, but have it copied.
Using my code sample? |
Yes the same key is going to be re-used over and over to encrypt different blocks. The key never changes once it is created. So that's why I think it's a good idea for it to do zero copy for each call using the same key.
You said it could work if I transfer it back. But that means the source thread reference becomes invalid, and it wouldn't work in concurrent situations where the source thread is sending the key to be used for encryption of different chunks concurrently.
I haven't tried with Node Buffer involving your example yet though.
…On 18 July 2021 3:59:03 am AEST, Andy Wermke ***@***.***> wrote:
> If the array buffer from the main thread is detached, how can I
re-use the key and plaintext buffers for a subsequent operation on
threadsjs?
That's why it's called *transferable* objects: You transfer the data
from one thread to another instead of copying it. Of course you cannot
use it in the source thread anymore, unless the second thread transfers
it back after it's done.
Your use case sounds as if you really aim to be able to use the keys in
both threads independently. If that's the case then you might actually
want to not transfer the data, but have it copied.
> Furthermore the Node Buffer's ArrayBuffer did not get detached, so I
found that it was still a copy.
Using my code sample?
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#348 (comment)
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
|
I get that, but it really comes down to copying vs. moving between threads. If you need it in both threads simultaneously you should copy it. What would make sense, though, is to copy it once only. So you might want to assign each key an ID or calculate a hash, copy the keys to the other threads once only and then pass their ID/hash on each call instead of passing the whole key. Can't help but think that this would be much easier to do if #273 was merged, so other threads can call the main thread (to request a key by it ID/hash if the key has not been cached in the thread yet). |
The other question is: What's the size of those keys? If it's insignificant compared to the size of the data that you want to encrypt, it might not even be worth optimizing… 😉 PS: #273 could also make it possible to pass streams to workers. Might be really valuable if you need to encrypt large amounts of data. |
I might have misinterpreted how the Transfer works. I thought it was
going to be a sort of shared memory construct where I can manipulate
that memory block in one thread, and have it read in the other thread.
But it seems that there has to be this explicit passing around of
references...
Yea so I could pass the key as a cache, but the keys are just going to
be 32 bytes long so it's not a huge deal.
But it's not that I have a large buffer to encrypt, it's that I will
have MANY small buffers to encrypt and decrypt. And I thought it would
be good to have subthreads operate on the shared memory rather copying
all the time.
Now I still have to check if Node buffers work, because from my
preliminary testing it doesn't work. Node array buffers are never
detached when I used `Transfer` on them.
…On 7/18/21 3:06 PM, Andy Wermke wrote:
So that's why I think it's a good idea for it to do zero copy for
each call using the same key.
I get that, but it really comes down to copying vs. moving between
threads. If you need it in both threads simultaneously you should copy it.
What would make sense, though, is to copy it once only. So you might
want to assign each key an ID or calculate a hash, copy the keys to
the other threads once only and then pass their ID/hash on each call
instead of passing the whole key.
Can't help but think that this would be much easier to do if #273
<#273> was merged, so other
threads can call the main thread (to request a key by it ID/hash if
the key has not been cached in the thread yet).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#348 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAE4OHNENZ2LFDC5ESFVVP3TYJOM3ANCNFSM4Z2WUT3Q>.
|
I think node.js Buffers should work in principle. However, have you considered just passing the array buffers? They are transferable as a whole and the node.js Buffers are basically just a fancy wrapper around them. Might be easier to pass the array buffers themselves and if you really need some of the Buffer functionality, you could The |
Oh this might explain why Node Buffer's array buffers never get
detached. The docs say that it cannot be detached. And inside Node
Buffer's ArrayBuffer it is never detached after Transfer.
I can use ArrayBuffer too, but so much of the other code uses Node
Buffers. This is being used for transparent filesystem encryption.
…On 7/18/21 7:23 PM, Andy Wermke wrote:
I think node.js Buffers should work in principle. However, have you
considered just passing the array buffers? They are transferable as a
whole and the node.js Buffers are basically just a fancy wrapper
around them.
Might be easier to pass the array buffers themselves and if you really
need some of the Buffer functionality, you could
|Buffer.from(arrayBuffer)| on the other side.
The |Transfer()| really /transfers/ ownership. It's gone from the
source thread afterwards. There is, however, a shared memory array
buffer: See SharedArrayBuffer
<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer>.
Should work with node.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#348 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAE4OHMQNY5XBVR65M2JJILTYKMQHANCNFSM4Z2WUT3Q>.
|
If I used |
I didn't try yet myself, but according to this answer you don't need |
Hi @andywer I think there's still a problem with
The second Also I have a question about this situation... In the actual method signature for the worker modules, we don't use |
I am not a 100% sure about the specifics anymore, but I will give it a shot…
I don't think you have to type the worker function return type as We would have to do the same thing with the worker function parameters, but there is one good reason why we don't need to: The worker function signature used in the main thread is derived from the worker's types, but we never use any main thread types in the worker, so we only need to do
What's the error? |
The error is:
The inferred type from
Do you see how the second parameter doesn't get the This is my "worker module" const dbWorker = {
async encrypt(
key: ArrayBuffer,
plainText: ArrayBuffer,
): Promise<TransferDescriptor<ArrayBuffer>> {
const cipherText = await utils.encrypt(key, plainText);
return Transfer(cipherText);
},
async decrypt(
key: ArrayBuffer,
cipherText: ArrayBuffer,
): Promise<TransferDescriptor<ArrayBuffer> | undefined> {
const plainText = await utils.decrypt(key, cipherText);
if (plainText != null) {
return Transfer(plainText);
} else {
return;
}
},
}; |
This is still a problem btw, the |
I have a worker function like:
However when I call this function from the main side:
There's a type error:
The problem appears that the return type is a
TransferDescriptor<ArrayBuffer>
.Then when I call it, it must also be
ArrayBuffer
.I have to typecast like this:
Is this the right way to do this?
The text was updated successfully, but these errors were encountered: