-
Notifications
You must be signed in to change notification settings - Fork 1
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
Type-safe read-only views #12
Comments
While I thinking, that |
@whyoleg Indeed, all my use-cases are always: write something to the buffer, and then make read-only views (possibly used concurrently) of the already wrote part of the buffer. So indeed, I would prefer to have a contract similar to We could then do: val source: Source = // ...
val buffer: Buffer = // ...
val read = source.readFully(buffer) // Or something similar
val head: ReadBuffer = buffer.takeHead(read)
// Pass head to some external api Also would it be possible to have an absolute variant of fun takeHead(startIndex: Int = readIndex, endIndex: Int = writeIndex): Buffer |
Im not sure, that it will be possible to have such an absolute variant, as if so it will split buffer in 3 parts (from 0 to start, from start to end, from end to capacity) and returns center part of it, so not sure, that it will be convenient for anyone. |
Well, then I could always do the following but that is not very elegant IMO: val head = buffer.takeHead(endIndex)
head.takeHead(startIndex).close()
// Use head and buffer... Also I still would need to be able to duplicate read-only views, in order to have multiple read views (possibly accessed concurrently) of the same slice of the underlying memory segment. |
Can you provide better sample (gist, link to repo) on what you are trying to achieve? |
@whyoleg I will cook a more complete example today. Thanks. |
So let me try to give a more complete example. This is the context of https://github.com/ptitjes/kzmq. Currently, I use interface SocketHandler {
val input: Source
val output: Destination
}
val sockets: List<SocketHandler> = // ... not important
val messages: Channel<ReadBuffer> = // ... not important
// At some point, I launch:
launch {
while (isActive) {
val message = messages.receive()
sockets.forEach { socket ->
val m = message.duplicate()
launch {
socket.output.write(m)
m.close()
}
}
message.close()
}
}
// When user wants to send some data:
val data: ReadBuffer = // ... buffer given by the user
messages.send(data) @whyoleg Is my use-case clearer? |
Yeah, thx! I see how this can be done. RSocket has similar requirements, so I know what you want Buuuut 😅 , I'm really interested about 'takeHead'/'slice' use cases, this part of design is much harder from my point of view |
@whyoleg Yeah I forgot to say that the
Either, I would:
Or, I could:
|
I feel like the val head = buffer.takeHead(size)
val readOnlyHead = head.someOperationToMakeAReadView() Should |
Thx! val buffer: Buffer = //retrieved from socket somehow
buffer
.stealReadOnly() //will return ReadOnlyBuffer instance, which will be view over buffer underlying storage; after this call, `buffer` will be empty, and have no underlying storage
.use { frame: ReadOnlyBuffer ->
val flags = frame.readByte()
val size = frame.readInt()
val data: ReadOnlyBuffer = frame.readBuffer(size)
//do anything with it
} Where I will summarize my idea in PR and will try to create it today-tomorrow. |
@whyoleg it would eleganty fit my use case. 👍 Thanks |
Why not rename |
API Prototype (no implementation yet) is here: https://github.com/ktorio/ktor-io/tree/whyoleg/read-only-buffer |
Overall it's out of scope of this issue, because it can depend on implementation of source, but I would think, that most of the time it will be not single TCP frame, but limited by some buffer inside implementation. But for now, I have no preferences or ideas on this. |
OK. But then this changes how we do use buffers. For instance, in your example, I cannot be sure that the returned read only buffer contains all the expected data, right? |
As I know, when you are doing some TCP work, you can't rely on an idea, that all data will be in one TCP frame. Amount of data in it depends a lot on buffers sizes of client and server. That's why we need to send data length when working with TCP. In my example There is no check for availability of data in buffer, yes, real world code should check, if it enough data left in buffer and then: or steal head, and wait more data, or just wait more data, if buffer has enough capacity, or fail if socket closed, and so on. And of course, if there will be not enough data in buffer, and you want to read buffer of length bigger than exists - we will fail, don't think that silent returning part of a buffer is a good idea here |
In most of my use-cases, I need a read-only view of buffers (both Buffer and CompositeBuffer) with:
It seems from e5l/view-and-refcount and whyoleg/resources that we are taking the route of having reference-counted underlying storages. So the following design proposal is based on this idea.
As a first step, I would propose that we extract a
ReadBuffer
interface fromBuffer
containing:var readIndex: Int
relative read index,val readLimit: Int
exclusive read limit index (always equal towriteIndex
inBuffer
's default implementation),fun read*()
relative read methods,fun get*At(...)
absolute read methods,fun copyTo*(...)
relative and absolute bulk read methods,fun duplicate(): ReadBuffer
method that returns a duplicate read buffer (with shared underlying storage but independentreadIndex
),fun slice(...): ReadBuffer
methods that returns sliced duplicates.Additionally, we would add the following to the
Buffer
interface:val writeLimit: Int
for "symmetry" (always equal tocapacity
)override fun duplicate(): Buffer
method that returns a duplicate buffer (with shared underlying storage but independentreadIndex
andwriteIndex
).The text was updated successfully, but these errors were encountered: