Conversation
|
cc: @kyri-petrou @jdegoes |
| assert(stripped)(isNone) | ||
| } | ||
| ), | ||
| suite("filter")( |
There was a problem hiding this comment.
Can you remove the Cause changes that are not related to this PR?
There was a problem hiding this comment.
funny, thought I branched out of the series/2.x branch,
even funnier, these are already merged
|
cc: @regiskuckaertz, |
| //I beleive the original assertion had wrong expectations | ||
| }(equalTo(Chunk(Right(3) /*, Left("Aie")*/ ))) |
There was a problem hiding this comment.
I'm confused, why do you think the original assertion was wrong? To me it seems like the correct one
There was a problem hiding this comment.
the sink succeeds while processing the element 3, at this point it has no leftovers, so it emits nothing to downstream and succeeds immediately without reading anything else from upstream. hence upstream never gets to execute the failing effect.
| test("correct leftovers behavior") { | ||
| assertZIO( | ||
| ZStream | ||
| .range(0, 20, chunkSize = 3) | ||
| .run(ZSink.dropWhile[Int](_ <= 10).collectLeftover) | ||
| .map(_._2) | ||
| )(equalTo(Chunk(11))) |
There was a problem hiding this comment.
Shouldn't this be Chunk(11, 12, ..., 19)?
There was a problem hiding this comment.
no, that exactly the bug.
once a sink succeeds it's not supposed to read anymore from upstream, due to chunking it may have leftovers it must write to downstream. in this case upstream emits chunks of 3, so when the sink completes it has a single leftover element (11) which is exactly what the assertion tests
| assertZIO( | ||
| ZStream(0, 0, 0, 1, 0, 0, 2) | ||
| .rechunk(3) | ||
| .transduce { | ||
| ZSink.dropWhile[Int](_ == 0) *> ZSink.head[Int] | ||
| } | ||
| .take(10) | ||
| .runCollect | ||
| //seems like the 'trailing' sink invocation is by design | ||
| )(equalTo(Chunk(Some(1), Some(2), None))) |
There was a problem hiding this comment.
I feel like the correct behaviour here should be Chunk(Some(1)) since we have ZSink.head[Int]?
There was a problem hiding this comment.
transduce (based on ZStream.fromSink) runs the sink repeatedly until upstream completes. each time the sink completes, its result is emitted and the cycle begins again.
in this specific example:
first invocation of the sink consumes 0, 0, 0, 1 and emits Some(1),
second invocation consumes 0, 0, 2 and emits Some(2),
third invocation operates on an empty stream and emits None, then the entire stream completes.
| in => { | ||
| val out = in.dropWhile(p) | ||
| if (out.nonEmpty) | ||
| ZChannel.write(out) *> ZChannel.unit |
There was a problem hiding this comment.
I'm not sure I follow what's going on here. In the case that out.nonEmpty == true, won't this write the current Chunk and stop reading? Meaning that any subsequent chunks won't be read?
There was a problem hiding this comment.
exactly, when out.nonEmpty is true the sink actually succeeds (with a unit), however it must first write the leftovers to downstream.
once the sink succeeds it will no longer read from upstream, it is possible for other sinks to keep reading the leftovers + remaining upstream (achieved by composing sinks, ie. using flatMap)
| .pipeThrough(ZSink.dropWhileZIO[Any, String, Int](x => ZIO.succeed(x < 3))) | ||
| .either | ||
| .runCollect | ||
| }(equalTo(Chunk(Left("Aie")))) |
There was a problem hiding this comment.
@kyri-petrou in this case the sink is still reading from upstream after consuming 1,2,2, so upstream actually gets to execute the failing effect.
| ), | ||
| test("late error")( | ||
| assertZIO { | ||
| (ZStream(1, 2, 3) ++ ZStream.fail("Aie") ++ ZStream(5, 1, 2, 3, 4, 5)) |
There was a problem hiding this comment.
@kyri-petrou , here you can see the same 'error handling' behavior with the head sink in case of a 'late' failure
| ), | ||
| test("late error")( | ||
| assertZIO { | ||
| (ZStream(1, 2, 3) ++ ZStream.fail("Aie") ++ ZStream(5, 1, 2, 3, 4, 5)) |
There was a problem hiding this comment.
@kyri-petrou , here you can see the same 'error handling' behavior with the head sink in case of a 'late' failure
| test("early error")( | ||
| assertZIO { | ||
| (ZStream.fail("Aie") ++ ZStream(1, 2, 3) ++ ZStream(5, 1, 2, 3, 4, 5)) | ||
| .pipeThrough(ZSink.head) |
There was a problem hiding this comment.
same as above, with an early failure this time
kyri-petrou
left a comment
There was a problem hiding this comment.
I'm still trying to wrap my head around ZSink and how it's supposed to work, but in the meantime we need to revisit any usages of ZSink.dropWhile and ZSink.dropWhileZIO and make sure that they'll work under the new behaviour. One usage I can see that is likely affected is in ZStream#dropWhileZIO
|
@kyri-petrou , this pr fixes dropWhileZIO as well. one way to 'wrap your head' about Enter chunks, I think a good reference for sinks behavior is Zsink.fold which many sinks (such as head) are defined in terms of. |
|
I think the part that I'm trying to wrap my head around is whether having As for |
|
@kyri-petrou , the re. the inconsistency in are you convinced about the validity of the fixes introduced by this pr? |
| def dropWhileZIO[R, InErr, In]( | ||
| p: In => ZIO[R, InErr, Boolean] | ||
| )(implicit trace: Trace): ZSink[R, InErr, In, In, Any] = | ||
| new ZSink(ZPipeline.dropWhileZIO(p).toChannel) |
There was a problem hiding this comment.
Shouldn't this change target the dropWhile pipeline?
There was a problem hiding this comment.
@hearnadam , not sure what do u mean by that,
implementing the sink in terms of the pipeline resulted with a bug since the pipeline keeps pulling the entire stream while the sink is expected to stop immediately.
it is possible to change the pipeline and implement it in term of the sink (combined with identity to make sure it keeps pulling) but I can't see a good reason to do so as it'd reduce the (pipeline's) code readability
|
@kyri-petrou @ghostdogpr , |
|
@kyri-petrou , thanks for pulling series/2.x into my branch, in short: a sink stops consuming as soon as it can produce a result, at this time it'd emit the leftovers to downstream. thanks @kyri-petrou for adding this |
|
@kyri-petrou , btw seems like what's more alarming is that it seems |
There are things that I still don't understand with ZSink. e.g., why does this happen? Is this valid or not? ZStream(0, 1, 2, 0, 2, 0, 2)
.rechunk(3)
.transduce {
ZSink.dropWhile[Int](_ == 0) *> ZSink.head[Int]
}
.take(10)
.runCollect // produces Chunk(Some(1), Some(2), Some(2), Some(2), None)Why does the 2nd Some(2) exist when we used In either way, the part that worries me the most about these changes is not their validity per se but the fact that it alters the underlying behaviour of |
|
re. re. change behavior, existing behavior is a bug. it may manifest itself in multiple ways:
most of these won't happen when using sinks the 'normal' way, they appear only when using operators like is there a process to test this kind of change at least on the zio ecosystem? major libraries like zio-http, zio-grc, zio-kafka, zio-aws (feel free to add more), perhaps by releasing a side branch and then running test prs on these projects? |
No, not at the moment at least. I recently proposed to create a nightly build of sorts that tests the latest ZIO snapshot against all the zio-* libraries to ensure that there aren't any regressions. In the meantime, the only option will be to search for usages of |
|
and another fun fact, seems like see this test: fails with: I'll take a stub at this as well |
Yes, this is the expected behavior (the trailing |
|
@kyri-petrou , searched for |
kyri-petrou
left a comment
There was a problem hiding this comment.
Sorry for the nitpicks, code in the streams package is quite difficult to read as is so I prefer to have it as clean as possible. Happy to merge once they're addressed
| new ZPipeline(loop) | ||
| } | ||
| )(implicit trace: Trace): ZPipeline[Env, Err, In, In] = | ||
| ZPipeline.dropWhileZIO(p(_: In).map(!_)) >>> |
There was a problem hiding this comment.
You can use p(_: In).negate by the way
|
|
||
| new ZPipeline(loop) | ||
| } | ||
| )(implicit trace: Trace): ZPipeline[Env, Err, In, In] = |
There was a problem hiding this comment.
A bit outside the scope of this PR but since we're at it:
When I first read dropUntil and dropUntilZIO I assumed that the method would drop until but not inclusive of the element where the predicate evaluated true. Might be good to change the scaladoc to mention that it will drop elements up to (inclusive) of the element where it evaluated true
There was a problem hiding this comment.
@kyri-petrou , since this text appears multiple times in the scaladocs, let's agree on the wording here, then I'm willing to make the change. either in this pr or a separate one.
| p: In => ZIO[R, InErr, Boolean] | ||
| )(implicit trace: Trace): ZSink[R, InErr, In, In, Any] = | ||
| new ZSink(ZPipeline.dropUntilZIO(p).toChannel) | ||
| ZSink.dropWhileZIO(p(_: In).map(!_)) *> ZSink.head |
Co-authored-by: kyri-petrou <[email protected]>
Co-authored-by: kyri-petrou <[email protected]>
Co-authored-by: kyri-petrou <[email protected]>
Co-authored-by: kyri-petrou <[email protected]>
adresses #9395