- This is the last basic pattern I'm gonna show you,
and then we're gonna start looking at some higher level
code that uses all of these patterns, but it's cancellation.
Cancellation deadlines are critical to software
because a task or a request cannot take forever.
Now, we're really gonna use the context package
that's part of the standard library for this,
so I'm gonna use this code to show you
some basic mechanics of how the context package does things.
Okay?
But ideally I wouldn't want to see this code in production,
I'd wanna see the use of the context package.
And I'll show you that next.
So, here's cancellation.
Notice that we're using a buffered channel of one,
we're getting some delayed guarantees here.
We're still signaling with string data,
we're gonna get some delayed guarantee, right?
Not a full guarantee, but also we're not gonna be
completely blind to when things happen.
Now, we do this again, here we are,
here's our, we are the manager,
we've got our path of execution,
we've created this buffered channel of one, right?
We've got a buffered channel of one.
And we go off, and we, again use that pattern
of wait for result, right?
We start this employee goroutine, the goroutine
knows exactly what work it is they have to do.
The key is is that this might be work
that one day we wanna cancel.
We might wanna cancel this work,
you see, if we do the work, if we do the work,
then we can't cancel it.
We can't 'cause we're blocked waiting for it to get done.
This is where we need another goroutine
if we wanna be able to cancel.
We can monitor how well we're doing,
and then tell this other goroutine, "Dude, stop."
So, this is what happens.
We do the work, and if the work finishes,
then this goroutine wants to perform the send, right?
We're looking to perform the send back to us,
and that send can happen automatically
because there's a buffer.
So, that data can go right in here,
we don't need a corresponding receive,
we've got a buffered channel of one.
So, here we are.
Let's imagine right now, we're on this work piece
right there, line 227, we don't know how long
this work is going to take,
but we've decided the following on line 232.
After we created the employee, we set up the TC variable,
and the TC variable says that this work has
100 milliseconds of time to get done.
If you go back to line 227, I've simulated that this
could potentially take anywhere from
one to 500 milliseconds, okay?
So, there's a good chance that
this work doesn't get done in time.
We're not willing to wait more than 100 milliseconds,
if that happens, we've got to move on, I'm sorry.
How do we set up that scenario?
Well, we go back to the select because the select
allows us to handle multiple channel operations,
both sends and receives at the same time.
So, look at the first case.
The first case is a channel receive, okay,
off the buffer.
So, here we are blocked on a receive, on a receive,
where we wanna receive off of this buffer,
and as soon as some data gets in there,
then we'll unblock.
But the next case is a channel receive on our timer.
I've got two channels right here,
one trying to receive on this,
and one trying to receive on our timer.
That call to time dot after, what it returns is a channel,
and it's a channel that the run time will add the current
time to after the duration of
100 milliseconds in this case is complete.
So, the run time, the run time, okay,
will come in, the run time will come in on our timer,
and this is our timer here, the run time
will come in on our timer, and then perform
the send for us at 100 milliseconds.
Or, this send right here will add data to the buffer,
which will allow this receive to unblock.
Which one's gonna happen first?
I have no idea, that's the point.
If it happens, if this send happens
within 100 milliseconds, guess what?
We execute this case.
If it doesn't happen within 100 milliseconds,
then we're gonna execute this case.
Either way, we have a guarantee that we are going to move
on within 100 milliseconds or less.
Now, there's a big, big, big caveat to
this pattern here I want you to see.
There is a bug that we would be creating
if I used an unbuffered channel.
If I used an unbuffered channel, we would have a very,
very big problem in this piece of code.
Let's walk through a scenario,
where if I do not use my buffered channel of one,
life gets very, very bad very quickly.
Okay, so here we are.
We've got our goroutine.
Now, the channel says that we want guarantees, right?
Absolute guarantees that a signal
being sent has been received.
So, we go off, and the employee is created,
it goes off and does some work, there it is.
Now, we are on there, on line 234,
we've got two situations again.
Remember, we can receive, okay,
we've got the channel receive from this employee,
so we're waiting, we're waiting on our receive
from the employee, or we've got our time out channel, right?
Our time out, where we're gonna potentially receive
directly from the run time.
We don't know which one's gonna happen first,
but let's say that the time out happened.
Let's say the time out happens, right?
If the time out happens, then we receive and send here,
what happens to this goroutine?
This goroutine then moves on, it moves on,
which means that we're no longer in this receive anymore.
We move on, and what happens when we move on?
Well, eventually this work is going to get done,
and when that work gets done on line 227,
this goroutine is gonna wanna perform a send.
It's gonna wanna perform a send,
but the send cannot complete without a
corresponding receive, we're using an unbuffered channel,
where we wanna guarantee that the signal being
sent has been received.
We are going to block forever, why?
Because there's no goroutine anymore that's receiving
on the channel, this blocks.
Whew, this is a goroutine leak,
this is a classic goroutine leak.
Goroutine leaks are gonna eventually cause issues
around memory, right, you're gonna start leaking memory.
But Go is really good at cleaning things up,
so sometimes these goroutine leaks don't show themselves
for hours, days, or weeks because Go is very good
at dealing with and minimizing memory.
But understand that when we deal with these
cancellation patterns, where the goroutine here,
us M, could walk away from the goroutine here
that's doing the work, and there's no way to tell
that goroutine to stop directly,
sometimes there is through networking calls,
but other times there isn't.
We've gotta make sure that that goroutine doesn't leak
by using the buffered channel of one.
So, this time, that send doesn't need a corresponding
receive, we've got a buffered channel here,
which means the send happens before the receive.
Okay, so these are things we're gonna look at.
And if you can visualize goroutines as people,
and visualize this idea of manager, employee,
put yourself always as the manager.
Think about these goroutines as doing work on your behalf,
you can start visualizing this orchestration right,
the interactions between these goroutines,
then you can start seeing whether or not
you're gonna block, whether or not you're gonna
deal with a high levels of latency,
or maybe you can reduce that.
We can use a select case to deal with drop patterns.
And we can start doing a whole bunch of stuff
once we start thinking about our signaling semantics
and visualize the interactions these
goroutines are going to have.