8000 output: rework LineFilter API as LineMap by bobheadxi · Pull Request #24 · sourcegraph/run · GitHub
[go: up one dir, main page]

Skip to content

Conversation

@bobheadxi
Copy link
Member
@bobheadxi bobheadxi commented May 16, 2022

Reworks the LineFilter API to include context and be more flexible to accommodate chaining patterns based on my thoughts in #14 and this thread, for example:

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	// Demonstrate that output streams live!
	cmd := run.Bash(ctx, `for i in {1..10}; do echo -n "This is a test in loop $i "; date ; sleep 1; done`)
	if err := cmd.Run().
		Map(func(ctx context.Context, line []byte, dst io.Writer) (int, error) {
			if bytes.Contains(line, []byte("loop 3")) {
				defer cancel() // Interrupt parent context here

				written, err := run.Cmd(ctx, "echo", "Loop 3 detected, running a subcommand!").
					Run().
					WriteTo(dst)
				return int(written), err
			}

			return dst.Write(line)
		}).
		Stream(os.Stdout); err != nil {
		log.Fatal(err)
	}
}

Definitely starting to get a bit complicated code-wise though. Also, to enable chaining patterns on LineFilter we can't flush the output until all LineFilters are executed, which can be quite awkward for streaming long-running commands. That said if those subcommand want streaming output, they should stream to os.Stdout instead, so maybe this all makes sense 😁

@bobheadxi bobheadxi requested a review from michaellzc as a code owner May 16, 2022 23:48
@bobheadxi bobheadxi requested review from a team and danieldides May 16, 2022 23:49
@bobheadxi bobheadxi marked this pull request as draft May 16, 2022 23:53
@bobheadxi bobheadxi removed request for a team, danieldides and michaellzc May 16, 2022 23:53
@bobheadxi bobheadxi marked this pull request as ready for review May 17, 2022 00:46
@bobheadxi bobheadxi requested review from a team, danieldides and michaellzc May 17, 2022 00:46
Copy link
Contributor
@jhchabran jhchabran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more. 8000

Definitely starting to get a bit complicated code-wise though.

I think this comes from the fact that we're using Filter as a primitive to create a Map method and we could probably abstract that away to make it simpler for the user.

Love the iterative approach on the API <3

if err := cmd.Run().
Filter(func(ctx context.Context, line []byte, dst io.Writer) (int, error) {
if bytes.Contains(line, []byte("loop 3")) {
defer cancel() // Interrupt parent context here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure to understand what we're trying to demonstrate here. We want to showcase that we're streaming the output, so both canceling and echoing the output in line 23 would achieve that.

The output we're getting makes sense, but the intention around the cancel function call is a bit confusing for the potential reader. I would add a comment on line 23 saying that we're never going to print this because of the cancel just above to clarify for the reader.

~/work/run/cmd/pollexample  linefilter-v2 $ go run ./...
go: downloading github.com/djherbis/buffer v1.2.0
go: downloading github.com/djherbis/nio/v3 v3.0.1
This is a test in loop 1 Tue May 17 09:58:48 CEST 2022
This is a test in loop 2 Tue May 17 09:58:49 CEST 2022
2022/05/17 09:58:51 signal: killed
exit status 1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it demonstrates an interrupt right now after a "ready" event, described in https://sourcegraph.slack.com/archives/C07KZF47K/p1652738100240049


// Pipe output via the filtered line pipe
filterErrC := make(chan error, 1)
writtenC := make(chan int64, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm mistaken, we don't need the buffered channels here, if we read the error before reading the written count.

To put it differently, the need for the buffering comes from the fact that we're writing into A then B, while we're reading from B then A. Writing in A then B and reading in the same order removes the need for the buffered channel.

All examples work with unbuffered channels when lines 152 and 153 are swapped.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think buffering it means the goroutine pushing to this channel can exit, otherwise it blocks IIRC

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right, though my point was we don't need it here if it works by just swapping around the order we're doing things. In my experience, buffered channels tend to postpone problems and can become harder to debug, so I tend to favor avoiding them unless it's absolutely needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the very messy 219a611 !

@bobheadxi
Copy link
Member Author
bobheadxi commented May 17, 2022

I think this comes from the fact that we're using Filter as a primitive to create a Map method and we could probably abstract that away to make it simpler for the user.

You're saying we should with this Filter API right?

I wonder if we should call it Map instead 🤔

@jhchabran
Copy link
Contributor

You're saying we should with this Filter API right?

Yeah re-reading myself, I wasn't very clear. My first thought was that if we want to do a Map as you're kind of doing in your example by calling a sub command, it would be nicer to address this use case with a function designed for that, discarding the dst parameter, as you may not want to write back to the stream what you do with the lines.

I wonder if we should call it Map instead 🤔

Filter is a specialization of Map, so I think in that case yes, it makes more sense. When I seen "filter" I think about passing a predicate to say if I want or not that line, whereas what we have here is indeed closer to Map.

@bobheadxi
Copy link
Member Author

it would be nicer to address this use case with a function designed for that, discarding the dst parameter, as you may not want to write back to the stream what you do with the lines

Hmm, I think that's a minor convenience (discarding dst) to make the API surface even wider for

I think you're right, Map is the right word here and we can start with this broader API for now

@bobheadxi bobheadxi changed the title linefilter: rework LineFilter API output: rework LineFilter API as LineMap May 17, 2022
@danieldides
Copy link

+1 to using Map over Filter. That brings the functionality much more in-line with what my brain thinks of when I see the words map/filter/reduce.

@bobheadxi bobheadxi requested review from a team and jhchabran May 18, 2022 16:36
Copy link
Contributor
@jhchabran jhchabran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Member
@michaellzc michaellzc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stamping on the API design, nice 🎉

(🙂 don't know go enough to make comments on the implementation)

(only if google SDK is a good boy and I can use it right away)

@bobheadxi bobheadxi merged commit b97146e into main May 18, 2022
@bobheadxi bobheadxi deleted the linefilter-v2 branch May 18, 2022 17:04
@bobheadxi
Copy link
Member Author

My first thought was that if we want to do a Map as you're kind of doing in your example by calling a sub command, it would be nicer to address this use case with a function designed for that, discarding the dst parameter, as you may not want to write back to the stream what you do with the lines.

@jhchabran I just remembered we do have this, using StreamLines!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

0