ROX-13661: Refactor Stopper and use it more#3931
Conversation
|
Images are ready for the commit at 46adb80. To use with deploy scripts, first |
pkg/concurrency/stoppable.go
Outdated
| // StoppableFlow represents an interface for goroutine to check and report status of a graceful shutdown. | ||
| type StoppableFlow interface { | ||
| // StopWithError initiates a shutdown due to an error that happened during async processing in goroutine. | ||
| StopWithError(error) |
There was a problem hiding this comment.
Shouldn't StoppableClient.Stop and StoppableFlow.StopWithError be closer together in the same object?
There was a problem hiding this comment.
This was intentional.
.Client().Stop() is called externally (outside of goroutine), and I haven't found any cases when non-nil error was provided as parameter. That's why .Client().Stop() doesn't take error parameters.
OTOH, I found goroutines self-stop due to error (e.g. a channel closed). That's why I added .Flow().StopWithError(error) for internal interface of goroutines (that's what I call StoppableFlow).
9372aad to
6209b25
Compare
6209b25 to
fb528ff
Compare
| func (c *commandHandlerImpl) Stop(err error) { | ||
| c.stopC.SignalWithError(err) | ||
| func (c *commandHandlerImpl) Stop(_ error) { | ||
| c.stopper.Client().Stop() |
There was a problem hiding this comment.
It looks like we are loosing information about the error passed here. As c.stopper.Flow().StopWithError(err) does not fit here, then maybe we need a replacement for sending a command to stop with error?
There was a problem hiding this comment.
No, we are not loosing information because the error is always nil.
It is exactly the same topic as above - #3931 (comment)
I recommend you to check with IDE Find Usages what calls this Stop() method and whether there's any single place that gets non-nil error. I found zero.
There was a problem hiding this comment.
I see, so the error was a "plan for later" that we never used
There was a problem hiding this comment.
Probably. Something of this kind, or API was not separated for internal/external (from goroutine point of view) use cases and external functions were just a copy of internal needs. I guess we'd never know unless we go in archeology.
32651d4 to
dcb444a
Compare
| } | ||
|
|
||
| func (s *NodeScanHandlerTestSuite) TestHandlerStoppedError() { | ||
| func (s *NodeScanHandlerTestSuite) TestHandlerStopIgnoresError() { |
There was a problem hiding this comment.
I think TestHandlerStopIgnoresError test became redundant and can be removed. Would it be ok with you @vikin91 ?
c2e3c6f to
5ea9da9
Compare
2f3dd15 to
3105f6a
Compare
| if s.stopC.IsDone() { | ||
| return false, errors.New("Cannot start reader because stopC is already done. Reader might have already been stopped") | ||
| } |
There was a problem hiding this comment.
This was redundant because nothing in the current code tries to stop the reader before it's started.
| return s.stopC.Signal() | ||
| func (s *auditLogReaderImpl) StopReader() { | ||
| s.stopper.Client().Stop() | ||
| _ = s.stopper.Client().Stopped().Wait() |
There was a problem hiding this comment.
Absence of this .Wait() line lead to the following error appear from time to time in logs when running tests.
collection/auditlog: 2022/11/30 12:19:56.751189 auditlog_impl.go:158: Error: Audit log tailer stopped with an error no such file or directory
Looks like I fixed a bug.
| // Returns true if the reader can be started (log exists and can be read). Log file missing is not considered an error. | ||
| StartReader(ctx context.Context) (bool, error) | ||
| // StopReader will stop the reader if it's started. Will return false if it was already stopped. | ||
| StopReader() bool |
There was a problem hiding this comment.
There was nothing that looks at the returned value of StopReader().
6b46a72 to
531b081
Compare
531b081 to
75bcb57
Compare
There was a problem hiding this comment.
Code looks good. Some general comments and thoughts. No blockers.
The distinction between Client and Flow is more clear now on the second pass.
However, I have one last optional suggestion: I wonder if there's a way to enforce Client/Flow distinction (at least as a code pattern) in a way that avoid misuse of the Stopper interface inside the goroutine? For other dev that wants to build a new sensor component the distinction might not be so obvious.
We don't want the code inside the component's goroutine to call stopper.Client() functions, only stopper.Flow(), right? So maybe the goroutine should be anonymous and receive only what it should operate on?
Something like:
func (c *MyComponent) Start() {
// do pre-work
go func(flow concurrency.Flow, dependency T) {
// Component main loop
}(c.stopper.Flow(), someOtherDependency)
}Maybe out of the scope for this PR. WDYT?
| type SensorComponent interface { | ||
| Start() error | ||
| Stop(err error) | ||
| Stop(err error) // TODO: get rid of err argument as it always seems to be effectively nil. |
597b26a to
18f666f
Compare
|
@fredrb Sorry, I missed your suggestion previously.
That's the ideal state I wanted to get it to but unfortunately couldn't find a way. Mainly the reason is that we use func (c *MyComponent) Start() {
// do pre-work
go func(flow concurrency.Flow, dependency T) {
// Component main loop
}(c.stopper.Flow(), someOtherDependency)
}there is
For me it is actually the opposite. We don't want anything outside of goroutine to use |
6c21226 to
2e17e81
Compare
|
/retest |
instead of its own stoppable implementation.
This has been a bit more involved and requires more thorough review.
although there's nothing that calls `Stop` except of tests and I'm not sure that counts.
2e17e81 to
46adb80
Compare
Description
Refactor existing
Stopperto be used in all places where we previously hadstopCandstoppedC.Induced by #3800
Note that I am not trying to fix things in this PR, just reduce the amount of different patterns. Some things look very questionable but I refrain from fixing them in the absence of unit tests.
It is best to review this PR by going through commits. This way the size isn't overwhelming.
Checklist
No need in the following:
Testing Performed