00:15 | <Domenic> | ok, back, although i should probably head out for the day |
00:16 | <terinjokes> | Domenic: you should go home |
00:16 | <Domenic> | this Promise.resolve analogy soothes me greatly |
00:16 | <Domenic> | (in general any time I can analogy to promises, I feel more confident) |
00:17 | <Domenic> | we can let pipeTo only accept true WritableStreams, and if in the future that becomes limiting we can add WritableStream.cast or similar as a mechanism for coping |
00:24 | <wanderview> | Domenic: https://github.com/whatwg/streams/issues/321 |
00:24 | <wanderview> | have a good night! |
06:37 | <JakeA> | annevk: https://github.com/slightlyoff/ServiceWorker/issues/607#issuecomment-90819078 - I don't understand "wait with overrideRequestURL" |
06:53 | <annevk> | JakeA: just not implement it |
06:54 | <annevk> | JakeA: what you argue for would be a new special case that did not exist before; the case the implementers brought up was not considered when we figured it as a shorthand for short-circuiting redirects |
06:58 | <JakeA> | annevk: we've got some internal customers for this, I'll find out if the API works for them. But yeah, in that case it isn't exactly like a redirect. |
06:58 | <annevk> | JakeA: it would be interesting to hear why they can't use Response.redirect() |
07:05 | <JakeA> | annevk: yeah, I'm digging into that and building them an example |
08:00 | <annevk> | Is anyone using https://atom.io/? |
08:41 | <philipj> | annevk: I think the general situation is that Content-Type is ignored for both media elements and <track> |
08:42 | <philipj> | there's a bit of a history, but since Microsoft caved and started ignoring it in IE I think there's no turning back (which I'm happy about) |
08:42 | <annevk> | philipj: so for X-Content-Type-Options: nosniff we want to give server administrators the feature of enforcing Content-Type for resources so they can't be used in an unexpected context |
08:43 | <annevk> | philipj: this requires a whitelist of MIME types for audio/video and a separate set for track |
08:43 | <philipj> | uh, ok, I couldn't say how that currently works |
08:44 | <annevk> | philipj: it doesn't for those contexts |
08:44 | <annevk> | philipj: well, it might in IE |
08:44 | <annevk> | I haven't tested IE yet |
08:44 | <philipj> | for <track> there's just no code that looks at Content-Type, at least not in Blink, if it's not WebVTT then it won't work |
09:33 | <annevk> | philipj: okay, so we could just make the whitelist text/webvtt |
09:33 | <annevk> | philipj: for CSS it's text/css at the moment |
09:33 | <annevk> | philipj: only unclear thing then is the MIME types for audio/video which is somewhat of a trainwreck |
09:45 | <annevk> | text/vtt* |
09:57 | <annevk> | http://w3cdreams.tumblr.com/ |
10:01 | <jgraham> | Wow, well I was enjoying that and then the Service Worker thing guenuninely made me feel ill |
10:10 | <annevk> | jgraham: hypno cat? |
10:12 | <jgraham> | Possibly |
10:13 | <jgraham> | Also made me unable to spell genuinely |
10:17 | <zcorpan> | just need to let the eyes follow the circles a few laps and then you'll feel better again |
14:52 | <wanderview> | Domenic: you said there were user space Promise libs that were faster than browser implementations... can you point me at one you recommend for perf? |
14:52 | <bradleymeck> | wanderview: bluebird |
14:52 | <Domenic> | yep |
14:56 | <wanderview> | Domenic: should I just be able to drop this in with the stream reference impl? |
14:57 | <Domenic> | wanderview: ugh, the reference impl is so un-optimized... |
14:57 | <Domenic> | wanderview: but, yeah, if you do global.Promise = require('bluebird') it should be drop-in-able |
14:58 | <Domenic> | But I mean, all the asserts (in loops too!) and try/catches and the hilarious queue-with-sizes implementation will dwarf the performance change I think |
15:01 | <wanderview> | Domenic: meeting now... but I'll try to describe the case I'm concerned with... I can write something not use ref impl, but just something similar to tease out the Promise impact |
15:03 | <Domenic> | wanderview: OK cool. Be sure it does actual I/O too :) |
15:04 | <wanderview> | Domenic: javascript consumers may not do actual I/O |
15:05 | <Domenic> | Ultimately the stream should be grounded in some I/O, most likely |
15:06 | <Domenic> | E.g. maybe your stream vends JS objects but it's derived from some stream that read from a file/network |
15:36 | <wanderview> | Domenic: is it not reasonable for js to do in-memory operations like your stream demo where its searching for a particular value? that is not I/O related |
15:36 | <Domenic> | wanderview: how is that no I/O related? |
15:37 | <Domenic> | wanderview: the stream is a fetch stream |
15:37 | <wanderview> | Domenic: I thought you meant on the consumption side you wanted it to be I/O |
15:37 | <Domenic> | wanderview: oh, no, i just meant that each promise produced from the reader should correspond to doing some I/O |
15:38 | <Domenic> | i.e., don't test for (let i = 0; i < 1000; ++i) { Promise.resolve(5).then(foo); }; test for (let i = 0; i < 1000; ++i) { fs.read(fd, ...).then(foo); }) |
15:39 | <wanderview> | Domenic: the main case I can think of that concerns me is where you write data to a pipe buffer... and then want to read it starting at some later time... in that case the pipe .read() is not related to I/O until the buffer is drained... and draining the buffer will be much slower than with sync .read() |
15:39 | <Domenic> | "much" slower citation needed... especially since sync read() would need to use async .ready or similar for backpressure signals |
15:39 | <wanderview> | Domenic: thats why I want to test with real Promises :-) |
15:40 | <Domenic> | sounds good :) |
15:40 | <wanderview> | Domenic: I agree at least one async operation is needed... but doing it on every buffered chunk seems potentially not good |
15:41 | <wanderview> | Domenic: I'm going to put this in the batch read issue... I think some kind of .readAllAvailable() would solve this... give me an array of all available chunks... so you get an array of length 1 or greater |
15:41 | <wanderview> | for cases where you just want to read as fast as possible |
15:41 | <Domenic> | wanderview: sounds good, yeah. Or 0 chunks if end of stream. |
15:52 | <wanderview> | Domenic: I guess my concern here started by looking at the current rs.pipeTo() implementation... since it waits for each .read() to resolve before calling .read() again... you can't get any pipelining going that way... and then I started to wonder if it should do two .read() calls... so while its writing the first its already started the async process to get |
15:52 | <wanderview> | the next, etc |
15:53 | <Domenic> | wanderview: right, but that should only delay at most a microtask, and then it will resolve and immediately call read() again, and the stack only has to unwind one frame since we're already in the microtask loop... it's one extra frame per loop iteration basically. |
16:02 | <JakeA> | wanderview: I've been thinking about cache.add[All]. I've been seeing a lot of examples check the status code of responses before putting them into the cache. Maybe we should revisit the idea of checking response.ok before caching (as an option). This would of course fail on all opaque responses. Do you think it's still useful? |
16:05 | <wanderview> | JakeA: in a meeting |
16:05 | <JakeA> | no rush |
17:06 | <annevk> | JakeA: should it not be added to fetch() then? |
17:15 | <wanderview> | JakeA: that sounds fine to me... I don't really have an opinion... whatever developers want/expect |
17:15 | <wanderview> | not sure I understand the opaque response thing, though |
17:15 | <annevk> | Domenic: the post-ES6 specification plan sounds rather lovely |
17:15 | <Domenic> | :) |
17:15 | <Domenic> | we'll see if we can pull it off... |
17:16 | <annevk> | Domenic: I wonder if WHATWG can gradually convert to that as well |
17:16 | <wanderview> | what plan? |
17:16 | <annevk> | wanderview: https://esdiscuss.org/topic/the-great-tooling-revolution |
17:18 | <Domenic> | annevk: you mean the two-impls thing? |
17:18 | <annevk> | Domenic: the tooling and writing style |
17:18 | <Domenic> | annevk: ah interesting. |
17:19 | <annevk> | having said that, ES6 is rather hard to digest, but some of the algorithm shorthands you're introducing seem nice |
17:19 | <Domenic> | Yeah, I was going to say, "Ecmaspeak"'s evolution into something more user-friendly is still in progress (cf. https://streams.spec.whatwg.org/#conventions) |
17:19 | <Domenic> | And it's unclear the value of Ecmaspeak vs. some kind of "spec ES" |
17:20 | <wanderview> | Domenic: what does chrome's shipping Response body stream do for a Response returned from cache.match()? does it stream from disk? |
17:20 | <Domenic> | wanderview: good... question...... tyoshino are you awake perchance? |
17:20 | <wanderview> | I mean... is that implemented in this first version |
17:20 | <Domenic> | oh hi yhirano_ is in here too |
17:21 | <Domenic> | Maybe the thing to do is ask on the blink-dev Intent to Ship thread? |
17:27 | <wanderview> | Domenic: ok, will do... mainly just curious |
17:28 | <wanderview> | Domenic: what is WritableStream.getWriter() you to mention in that issue update? I don;t see that in the spec |
17:59 | <Domenic> | wanderview: https://github.com/whatwg/streams/issues/319 :-S |
18:00 | <wanderview> | ok... so not spec'd yet |
18:00 | <wanderview> | wasn't sure if it was coming or previous-and-gone |
18:01 | <Domenic> | heh... yeah |
18:01 | <Domenic> | wanderview: related https://github.com/whatwg/streams/commit/d757e05cf27f73488d13825e51ce5706a759ab27 |
18:04 | <wanderview> | Domenic: I didn't want to bikeshed more in the "getReader is named poorly" issue... but take() is also widely used for locking concepts :-) |
18:09 | <Domenic> | meeeeeeeeeeehhhhhh |
18:12 | <wanderview> | Domenic: getReader() does not unlock if the reader gets GC'd right? or rather, the whole stream has to be GC'd in addition to the reader? |
18:12 | <Domenic> | wanderview: right. (and in the latter case i'm not sure there's a difference.) |
18:12 | <Domenic> | wanderview: webkit has a test showing this |
18:13 | <Domenic> | unsure how/whether to put it in the general suite... |
18:13 | <wanderview> | Domenic: I really dislike stuff with explicit release like revokeObjectUrl()... so many leaks in code that use them... the error paths are atrocious |
18:14 | <wanderview> | from my experience with code in fxos trying to use that kind of API |
18:14 | <Domenic> | wanderview: well this case is pretty different, since it's not like you could successfully get another reader by waiting for GC of the first one, since that's unreliable |
18:15 | <Domenic> | that's a fair argument for the lifecycle management thread though |
18:15 | <wanderview> | yea |
18:15 | <wanderview> | I think I mentioned it there |
18:16 | <wanderview> | Domenic: is there a finally thing on promises for people to always return the reader? |
18:16 | <wanderview> | maybe that makes it less of a problem |
18:17 | <Domenic> | wanderview: we've been wanting to add finally to promises for a long time, and this cancelable promise stuff might push it over the line. |
18:19 | <wanderview> | Domenic: I guess its not a huge problem here... since lock is released automatically on close |
18:19 | <wanderview> | its optimized for single reader case... which is what we want |
18:24 | <Domenic> | wanderview: yeah, that's kind of the idea. if anyone's doing something tricky like parsing a header portion then passing the partially-read stream on to someone else, they can probably remember to release the lock. Although I agree finally would be nice. |
18:25 | <wanderview> | Domenic: that seems like something easier down with pipeThrough()... although it would be nice to remove the header transform piece after its done |
18:25 | <Domenic> | hmm yeah that's true |
18:26 | <wanderview> | ^down^done |
18:26 | <Domenic> | i guess most uses of readers are hidden behind the higher-level methods like pipe*() and tee() |
18:42 | <calvaris> | sorry, I just fell and I don't know if you read what I said but |
18:42 | <calvaris> | wanderview: I have a pull request with that test |
18:42 | <calvaris> | all custom tests that we had for WK are now there and waiting for Domenic to merge them |
18:43 | <wanderview> | calvaris: which test? |
18:44 | <calvaris> | the one for the garbage collection regarding the reader |
18:44 | <wanderview> | Domenic: btw, we apparently do implement microtask queue in gecko now |
18:44 | <wanderview> | calvaris: ah, cool |
18:45 | <Domenic> | wanderview: ah that's excellent |
18:45 | <wanderview> | Domenic: I still don't think our c++ can optimize around them, though... since js can call into c++ with js still on the stack |
19:59 | <trevnorris> | Domenic: "Cheaper than I/O" doesn't say a lot and Promise instantiation speed isn't the only thing to consider. It's the complete time from .push() to .read(). |
21:23 | <wanderview> | Domenic: what does it mean for a ReadableStream to .pipeTo() a WritableByteStream... what if the RS produces non-byte things? |
21:23 | <Domenic> | wanderview: calling .write() (or the equivalent internal operation if we move to that model) will error, causing the pipe to error. |
21:24 | <wanderview> | Domenic: but if the ReadableStream passes Uint8Array chunks, then its ok? |
21:24 | <Domenic> | wanderview: yes, I would really like that to work |
21:25 | <Domenic> | wanderview: maybe allow any of the "BufferView" types |
21:25 | <wanderview> | or just ArrayBuffer, I presume? |
21:28 | <Domenic> | wanderview: right, I forgot what the type was called that is a union of all typed arrays + ArrayBuffer + DataView |
21:28 | <Domenic> | "BufferSource" apparently |
21:30 | <wanderview> | Domenic: where is the latest ReadableByteStream proposal? |
21:30 | <Domenic> | wanderview: https://github.com/whatwg/streams/blob/asyncbytestream/BinaryExtension.md |
21:32 | <wanderview> | Domenic: so... its starting to feel to me like we should not have a ReadableByteStream at all... if the underlying source of the stream operates on bytes, then the specialization has already occurred... getReader() can just return a ByobReader |
21:32 | <wanderview> | or am I missing something? |
21:32 | <TabAtkins> | bring-your-own-beerReader |
21:33 | wanderview | didn't name it. :-) |
21:33 | <wanderview> | ByobByteStreamReader |
21:34 | <Domenic> | wanderview: hmm, but adding the BYOB layer changes things quite a lot, adding complexity specifically for the byte case. You would advocate building that complexity into the readable stream itself? |
21:35 | <wanderview> | Domenic: if a view is not passed to .read()... can it not just do a "get me the next chunk" semantics pretty easily? |
21:35 | <trevnorris> | Domenic: spec probably can't define any implementation details, can it? |
21:35 | <Domenic> | wanderview: for example the underlying source needs some kind of hook read(view) -> promise that fills view, and inside the stream/reader mechanisms you need something such that byobReader.read(view) transfers view to view2 then passes view2 to the read hook on the source |
21:35 | <Domenic> | trevnorris: it can define anything observable |
21:36 | <Domenic> | trevnorris: including creation APIs |
21:36 | <Domenic> | wanderview: the idea is you should opt in to one mode or the other (BYOB or auto-flowing) |
21:37 | <wanderview> | Domenic: then keep it getByobReader(), but have it reject on streams that don't have the right underlying source |
21:37 | <Domenic> | for example BYOB doesn't have a high water mark or queue in general (although it does probably keep one around since kernel buffers are finite... unsure if that's observable to spec or can be part of the underlying source details) |
21:37 | <Domenic> | wanderview: ick, that's a very bad API. It would be like Node having a tagName that throws an exception when you try to use it on Comment nodes |
21:37 | <trevnorris> | Domenic: my implementations tie a C++ class to every JS stream, on which a class method can be defined to receive incoming data. .pipe() can detect that and short circuit the JS call by passing from the internal C++ to the implementation defined method. |
21:38 | <trevnorris> | unfortunately it depends on certain V8-isms that i'm not sure are currently possible in other engines. |
21:38 | <Domenic> | trevnorris: right, in that case the spec's job is to make your short circuit unobservable |
21:38 | <Domenic> | trevnorris: and that means completely unobservable, even if someone overwrites dest.write to log when called or similar ;) |
21:38 | <wanderview> | Domenic: what does ReadableByteStream get you beyond the Byob capacility? It doesn't seem like anything... so maybe it should be a ByobReadableStream |
21:39 | <Domenic> | wanderview: yes, that is indeed the real delta. We figured it was a more user-friendly name? But no real opposition to renaming it |
21:39 | <wanderview> | Domenic: I guess the problem with that is the producer of the stream does not know if the user wants Byob semantics |
21:39 | <Domenic> | Actually we are kind of hoping not to use the term "BYOB" in public API. The fact that's in tyoshino's doc is largely just as a placeholder :) |
21:39 | <wanderview> | ^user^consumer |
21:40 | <wanderview> | I know |
21:40 | <wanderview> | Domenic: it just feels the "type" of the stream is determined by the underlying source... and having to have a special shell around particular underlying sources is clumsy... ReadableStream should just enable the extra features if its constructed with a byte oriented underlying source |
21:40 | <Domenic> | wanderview: the hope is that it is efficient for streams like fetch/fs/etc. that are bytes-backed to implement ReadableByteStream, and then consumers who want to opt in to BYOB behavior can use .getByobReader(), and normal people (including those agnostic to the chunk type) can just use .getReader() |
21:41 | <Domenic> | wanderview: I don't agree with that. The logical conclusion of that argument is that streams should be able to bake me a cake if I give them the right underlying source. |
21:42 | <Domenic> | Adding fundamentally new capabilities, instead of just different behavior, should require a new type. |
21:42 | wanderview | likes cake. |
21:42 | <wanderview> | Domenic: what do you anticipate being different about a WritableByteStream? |
21:43 | <Domenic> | wanderview: hazy right now, but one big thing is it will need to detach any passed-in buffers since they might be sent off thread |
21:44 | <wanderview> | Domenic: that... seems hard without including it in the WritableStream contract |
21:44 | <Domenic> | hmm how so? |
21:45 | <wanderview> | Domenic: isn't that an optimizaiton that should be negotiated by native underlying source to native underlying sink? |
21:45 | <Domenic> | wanderview: I am talking about var a = new Uint8Array([5, 10, 15, 20]); fileStream.write(a) |
21:46 | <Domenic> | I want to send the backing memory of a off into the thread that does file I/O |
21:46 | <Domenic> | and I am pretty sure we don't want to let people do a[0] = 7 and have that mutate the memory, maybe before the write, maybe after the write |
21:46 | <wanderview> | Domenic: you're talking about effectively making the chunk disappear from content... if under normal circumstances I can do ws.write(chunk); muchAroundWithChunk(chunk); ... how will code based just on WritableStream know that .write() suddently doesn't work that way because it has a WBS? |
21:47 | <wanderview> | Domenic: I guess I'm saying WritableStream.write() contract should say "don't expect the chunk to exist in its current form after this call" regardless of WritableStream vs WritableByteStream |
21:47 | <Domenic> | wanderview: that's fair, although I'm not sure in practice how worried we should be. But one easy fix is to add .getTransferringWriter() or similar, whereas .getWriter() does copies?? I dunno. |
21:48 | <Domenic> | wanderview: I just have no idea how to enforce that contract |
21:48 | <Domenic> | if it's not true for most writable streams, then people might assume it, no matter if the spec has some kind of "don't assume this please" note. |
21:48 | <wanderview> | Domenic: maybe its not enforceable... but we can at least say "told you so" when people' stuff breaks :-) |
21:48 | <Domenic> | heh, ok |
21:49 | <wanderview> | Domenic: it just seems stuff like ByobReader and maybe this TransferringWriter are opportunistic things... optimize in this fashion if its available... otherwise use the lesser stuff |
21:49 | <wanderview> | Domenic: and I guess in your mind the right way to "check if its available" is to do an instanceof on the prototype? |
21:50 | <Domenic> | wanderview: I don't see how to make ByobReader opportunistic. E.g. no way to do https://gist.github.com/domenic/65921459ef7a31ec2839#reading-a-file-chunkwise (old API I think) without explicitly doing things with JS |
21:50 | <Domenic> | wanderview: check if what is available? |
21:50 | <wanderview> | Domenic: check if ByobReader is available on a RS, for example |
21:50 | <Domenic> | wanderview: if (rs.getByobReader) { ... } |
21:52 | <wanderview> | Domenic: can't see much difference between that and getByobReader() returning null if its not supported |
21:53 | <Domenic> | wanderview: it's just basic API design... we don't add everything onto a single object. New classes of objects get ... new classes. |
21:53 | <Domenic> | Node vs. Element, etc. |
21:53 | <wanderview> | Domenic: I think I dislike mixing that with the revealing constructor pattern... the constructor is not revealing all the behavior |
21:53 | <wanderview> | Domenic: if we had a revealing factory method that could construct the right type... it might seem a bit better to me |
21:54 | <Domenic> | wanderview: I don't see how they're related. Revealing constructor pattern lets you customize a type's behavior. You can have different revealing constructors for different types. We don't use the same type for Promise and ReadableStream, even though they both have customizable behavior via the revealing constructor pattern. |
21:54 | <Domenic> | why would you say the constructor is not revealing all the behavior? |
21:55 | <wanderview> | Domenic: why would you ever do new ReadableStream(myByteSource)? shouldn't you *always* do new ReadableByteStream(myByteSource)? |
21:55 | <Domenic> | wanderview: yes? |
21:56 | <Domenic> | did i say otherwise? |
21:56 | <wanderview> | Domenic: no... but it feels weird to leave that as a footgun for people |
21:56 | <Domenic> | it's the same footgun as new Promise(myByteSource) ... not really worried. |
21:56 | <wanderview> | the type is really associated with the source, not the wrapper object |
21:56 | <Domenic> | or new WritableStream(myByteSource) |
21:57 | <wanderview> | Domenic: except those will fail... ReadableStream(myByteSource) will work, but just prevent consumers from optimizing |
21:57 | <Domenic> | The source is an adapter between the conceptual source and the concrete readable stream type |
21:57 | <Domenic> | wanderview: why would it work? |
21:58 | <wanderview> | Domenic: err... from what I can tell new ReadableStream() and new ReadableByteStream() expect the same properties on the passed duck typed source? |
21:58 | <wanderview> | is that not true? |
21:58 | <Domenic> | wanderview: the argument for ReadableByteStream() is not specced at all yet :) |
21:59 | <Domenic> | wanderview: it will probably be something like { start() { }, read(view), cancel(reason) { } } |
22:00 | <wanderview> | ok |
22:01 | <wanderview> | Domenic: going back to WritableByteStream... one optimization we have in our native streams is the writing side can pass its destination back into the person doing the write... so in theory this could be passed back to read(view) or something to all a ReadableByteStream to write directly into a WritableByteStream |
22:02 | <Domenic> | wanderview: good point, i forgot that was also something we definitely want out of ReadableByteStream + WritableByteStream pipes |
22:03 | <Domenic> | they can set up a small buffer pool and reuse buffers to limit total consumption and GC churn ... it will work beautifully ... /me waves his hands |
22:15 | <wanderview> | Domenic: sorry... I'm used to the DOM stuff which just enforces types and then says "using X's internal thing, do stuff" |
22:17 | <Domenic> | wanderview: np, just concerned about the layering in the design. Ideally *ByteStream should be additive and opt-in, both for consumers and from an architectural level. |
22:20 | <wanderview> | Domenic: to be honest, things like off-main-thread piping are mostly interesting to me for *ByteStream... for streams with potentially arbitrary js objects for chunks... not sure I can safely move those around off-thread |
22:20 | <wanderview> | not sure if that changes anything |
22:20 | <Domenic> | wanderview: oh, no, I was never really planning on them being off thread... but i want the model to not change drastically when you move from non-byte to byte streams |
22:21 | <Domenic> | wanderview: although I guess it could be pretty useful in some cases e.g. if a UA provided stream wants to pass metadata with each chunk like { remotePort, remoteAddress, data } or something instead of just data |
22:22 | <wanderview> | Domenic: I built a bunch of node stream libs to do that kind of thing before... not sure anyone really liked it much |
22:22 | <wanderview> | https://blog.wanderview.com/blog/2013/03/01/composable-object-streams/ |
22:23 | <Domenic> | wanderview: do you have a summary of where we are on https://github.com/yutakahirano/fetch-with-streams/issues/30 ? when i went to bed last night i think we were convering on new Request({ body: readableStream }) + fetch(request, wsRevealer) + cache.add(request, wsRevealer) or similar. But then it changed overnight and now I am confused. |
22:24 | <wanderview> | Domenic: I think DOM APIs would prefer to return a structured webidl object with a stream property (like Response) |
22:24 | <Domenic> | ? |
22:24 | <wanderview> | Domenic: I objected to putting wsRevealer on the consumer... because Request is no longer representative of the network request |
22:24 | <Domenic> | (I like the UDP object streams BTW!) |
22:25 | <Domenic> | Hmm was it ever? |
22:25 | <Domenic> | Isn't Request more like RequestMetadata? |
22:25 | <wanderview> | Domenic: no... before streams came into it, it contained all info to perform a network request |
22:25 | <wanderview> | including the body |
22:26 | <Domenic> | right, info to perform a network request, but not a network request itself.... |
22:26 | <Domenic> | which is why you can store it in a cache; you can't store a network request in a cache... |
22:27 | <wanderview> | Domenic: correct... it is a representation of a possible network request... it is not an actual in progress network request... but if you move the body ws-revealer to fetch() then Request no longer fully describes the possible network request |
22:27 | <Domenic> | hmmm |
22:27 | <Domenic> | i guess that's true |
22:27 | <Domenic> | and ws-revealer is really "a sequence of instructions for how to create a body" so it still goes in the category of "representation of a possible network request" |
22:27 | <wanderview> | Domenic: look at my last proposal... it combines your WritableStream wrapper without the hard coded type switch |
22:28 | <Domenic> | Although I doubt you'll store the ws-revealer in the cache... |
22:28 | <Domenic> | Yeah, I liked that, although unsure how it fits with the rest of the discussion up until that point |
22:28 | <wanderview> | Domenic: well, right now Cache only supports GET... so can't put a body in... but if you could, Cache would trigger ws-revealer to get the body data |
22:29 | <wanderview> | I still have all the code in gecko to store Request bodies |
22:29 | <Domenic> | hmm i see |
22:29 | <Domenic> | yeah i guess that makes sense |
22:29 | <Domenic> | you reify the body whenever the request gets "committed" somewhere |
22:30 | <wanderview> | those are fancy words, but I will nod my head |
22:30 | <Domenic> | :P |
22:30 | <wanderview> | does reify just mean normalize? |
22:30 | <Domenic> | nah ... i'm thinking of it as, make a real set of bytes out of a function that represents a way to get bytes |
22:31 | <wanderview> | ah, ok... serialize then |
22:31 | <Domenic> | ws-revealer is a "potential body" that gets reified into a real body when you fetch/cache-add |
22:31 | <Domenic> | yeah that i guess |
22:31 | <wanderview> | I should look up that term I guess |
22:31 | <wanderview> | I have more of an EE background so I tend to get lost in the CS theory world |
22:32 | <Domenic> | might be a mathematician thing, I dunno. Or just a pretentious thing :P |
22:32 | <Domenic> | so the current proposal is req.{setWriter/pipeTo} plus ... body(ws) { ... } which gets triggered after setWriter/pipeTo plus ... do we allow body: readableStream? |
22:33 | <wanderview> | Domenic: I still want body: readableStream, yes |
22:33 | <Domenic> | poor overloaded body: option |
22:34 | <wanderview> | Domenic: didn't you hear? fetch is mostly about sugar :-) |
22:35 | <wanderview> | Domenic: and to be clear... I only think we need this ws-revealer thing because of the desire to have progress notification that a pipe would obfuscate |
22:37 | <wanderview> | Domenic: btw... resolving the .write() promises when written to the kernel is going to be somewhat challenging... in gecko we get notification from the network code about progress in a different path from where we write... so we have to match that progress back up to the promises to resolve, etc |
22:37 | <wanderview> | maybe not challenging... but annoying |
22:39 | <Domenic> | :-S |
22:39 | <wanderview> | much easier to integrate that with a progress event (which is what it was designed for, of course) |
22:39 | <Domenic> | Well, there's https://github.com/whatwg/streams/issues/316 ... |
22:39 | <trevnorris> | wanderview: doubt this'll have any affect on the spec here, but in previous implementations I've had calls like .write() return a request object so you can trace the status at any point in the future. |
22:40 | <trevnorris> | e.g. .progress() to see how much has been written. |
22:40 | <trevnorris> | I use that in conjunction with timeouts to cancel writes that are taking too long. |
22:40 | <wanderview> | Domenic: for example, I think gecko network code throttles progress notifications to once every 50ms or something... so you will see batches of .write() promises resolves at the same time, etc. |
22:41 | <wanderview> | trevnorris: sounds like you want cancellable promises :-) |
22:41 | wanderview | trolls |
22:41 | <Domenic> | wanderview: lol, because the spec says 50 ms, good times :P |
22:41 | <trevnorris> | hehe. ;) |
22:45 | <trevnorris> | wanderview: is it possible to do something like: var req = ws.write(data); setTimeout(function(req) { if (req.status() != 'complete') req.abort(); }, 1000); ? |
22:47 | <wanderview> | trevnorris: I think you would have to call ws.abort() instead of req.abort() |
22:47 | <trevnorris> | wanderview: what if you did ws.write(data1) ws.write(data2) and I only wanted to abort writing data1? |
22:47 | <Domenic> | with cancelable promises you could do `var p = ws.write(data); setTimeout(() => p.cancel(), 1000). (Assuming calling p.cancel() does nothing on an already-settled promise) |
22:48 | <trevnorris> | already-settled promise? |
22:48 | <wanderview> | trevnorris: that seems racy to me... you may end up with data1 and data2 or just data2... also, not all write operations are abortable once they start, etc |
22:48 | <Domenic> | already fulfilled or rejected |
22:49 | <Domenic> | yeah, how does that work in POSIX? |
22:49 | <trevnorris> | wanderview: if you've queued up several chunks of data to be written, and only the first has actually been sent to the kernel it should be possible to remove any specific write req from the queue. |
22:50 | <wanderview> | trevnorris: in a multi-threaded environment... it may have been sent to the kernel and you just haven't been notified of it yet |
22:50 | <boogyman> | can you elaborate on a scenario where a Promise could be resolved while a "child" Promise has yet to resolve? |
22:50 | <bradleymeck> | its important to remember cancellation does not mean abrupt termination |
22:50 | <bradleymeck> | just that it should cancel at the next point |
22:50 | <Domenic> | boogyman: what is a child promise |
22:50 | <trevnorris> | wanderview: as soon as the data is handed off to something else I'll consider it unreachable. in the case of Node I know because we make the call to uv_write() directly. |
22:50 | <bradleymeck> | if there is not a clearly defines point of cancellation, something should not be cancellable |
22:51 | <bradleymeck> | defined* |
22:51 | <boogyman> | Domenic: however you define "p.cancel()" on an already resolved p |
22:51 | <Domenic> | i don't understand |
22:51 | <Domenic> | please phrase your question using code? |
22:51 | <wanderview> | trevnorris: "something else" is another thread? or the kernel? because in multi-process browsers it goes js->c++->IPC->c++->kernel with thread and process switches in there |
22:51 | <bradleymeck> | if cancellation is inteded to be similar to abort()/halting a thread it should be rethought |
22:52 | bradleymeck | can't type today |
22:52 | <wanderview> | right now ws.write() does not return a cancellable promise... |
22:52 | <wanderview> | this is all hand wving |
22:52 | <wanderview> | waving |
22:52 | <trevnorris> | wanderview: anything that takes control away from us over the lifetime of the data. but if I have an array of data chunks and only the first has been sent to uv_write() then the others should be able to be removed from their position in the queue. |
22:52 | <boogyman> | `var p = ws.write(data); setTimeout(() => p.cancel(), 1000). (Assuming calling p.cancel() does nothing on an already-settled promise) <-- Under what circumstance would be have already been resolved if it is dependent upon ws.write(data) |
22:53 | <boogyman> | would p.cancel()* |
22:53 | <wanderview> | trevnorris: does uv_write() do file writing on a separate IO thread or the main thread? |
22:53 | <Domenic> | boogyman: the adjective "resolved" does not apply to the function call p.cancel(), nor to its return value (which is undefined) |
22:53 | <trevnorris> | wanderview: main thread. |
22:53 | <bradleymeck> | wanderview: but I think at that point the cancellation point would be if the kernel gets it still, once it gets to the kernel it cannot be stopped, so attempts at cancellation would need to propagate to the C++ that flushes to the kernel, and if it has started flushing it is in an uncancellable state |
22:53 | <Domenic> | trevnorris: false? |
22:54 | <trevnorris> | wanderview: though I agree that if the data chunks were immediately sent to another thread to be written then we would have "lost control". thus cancel would only be a notification that we no longer need to be notified of its completion. |
22:54 | <trevnorris> | Domenic: eh? |
22:54 | <Domenic> | trevnorris: fs writes in io are done in a threadpool? |
22:54 | <wanderview> | trevnorris: ok, then you don't have to deal with the races I do... trying to pick out a single buffer to cancel is going to be hit or miss in browsers or other multi-threaded environments |
22:54 | <bradleymeck> | they are queued on the main thread though |
22:54 | <trevnorris> | Domenic: they're a special case. uv_wirte() and uv_try_write() is always done on the main thread. |
22:55 | <trevnorris> | *uv_write() |
22:55 | <bradleymeck> | ? |
22:55 | <Domenic> | trevnorris: ah, I thought we were talking about file I/O since wanderview asked "does uv_write() do file writing". I guess uv_write is for sockets? |
22:55 | <trevnorris> | yes. sorry I missed that. uv_write() is only for sockets. |
22:56 | <trevnorris> | filesystem I/O is a pain thanks to kernel incompatibilities. |
22:56 | <Domenic> | gotcha |
22:56 | <wanderview> | Domenic: what I am getting is they treat the "write is complete" state when it leaves main thread... which might not quite be to kernel |
22:56 | <Domenic> | yeah |
22:56 | <wanderview> | Domenic: which is different from your goal of "bytes written to kernel" |
22:56 | <wanderview> | I'm not sure to-kernel is all that much better than an app internal checkpoint |
22:56 | <Domenic> | if you are sure that writes respect backpressure, maybe "accepted and queued" is a good enough proxy for upload progress... |
22:56 | <wanderview> | but willing to try to support it |
22:57 | <trevnorris> | it's possible to share memory on the req across threads so read-only fields can be used to check its status. |
22:57 | <wanderview> | trevnorris: you have an atomic check-and-set for cancellation across threads? |
22:57 | <bradleymeck> | wanderview: I think at either point cancellation is a suggestion to w/e you handed it off to |
22:58 | <bradleymeck> | it needs to be able to continue the work if it has started side effects already |
22:58 | <bradleymeck> | no real need for locking to my knowledge |
22:58 | <wanderview> | bradleymeck: exactly... I guess I was just getting at I find it hard to reason about the need to cancel buffer1 and let buffer2 through... when you cannot know if you got to buffer1 in time |
22:58 | <trevnorris> | wanderview: right now only have it so if I've queued up many small buffers to be written I can check how many of them have actually been sent to the kernel. |
22:58 | <bradleymeck> | i don't think you should be allowed to know at the time of cancellation |
22:59 | <trevnorris> | wanderview: streaming video for example. say you're buffering data to be written. it's better that frames are lost and the data stays current then making sure all the data goes through. |
23:00 | <bradleymeck> | wanderview: to rephrase, I cannot think of a good reason you should be allowed to know that buffer1 was cancelled before it calls .finally |
23:00 | <wanderview> | Domenic: I think we are still not on the same page here: https://github.com/yutakahirano/fetch-with-streams/issues/30#issuecomment-91057900 |
23:01 | <bradleymeck> | which to me resolves the problem of reasoning |
23:01 | <wanderview> | I guess I'm happy we don't yet allow canceling individual .write() calls... and if we did, I think it would very much have to be a "best effort" |
23:02 | <wanderview> | bradleymeck: sure... I think I understand better that its a best effort cancel, for example the video streaming case mentioned |
23:02 | <bradleymeck> | which is good, I want "best effort" and not guaranteed |
23:03 | <bradleymeck> | cause once side effects start / external systems are involved you need to let them resolve back to valid states (say if you have not finished writing) |
23:03 | <bradleymeck> | guaranteed cancel would require external systems to stay in valid and buffered states which is a no-no |
23:03 | <wanderview> | Domenic: I'll respond in bug... but I think very much "setWriter()" should function like a pipeTo()... we could call it drainToWriter() or something if you want |
23:06 | <trevnorris> | wanderview: doing an atomic field set so the writing thread can check if the queue from another thread is still needed is very much possible. |