-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(job)!: eliminate data race; add callback #76
Conversation
Codecov Report
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. @@ Coverage Diff @@
## master #76 +/- ##
==========================================
- Coverage 90.04% 89.70% -0.35%
==========================================
Files 10 10
Lines 643 709 +66
==========================================
+ Hits 579 636 +57
- Misses 43 50 +7
- Partials 21 23 +2
|
@tychoish, @rfyiamcool, @tliron, it would be great to hear your thoughts on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a good pr . 👍 👍 👍
quartz/job.go
Outdated
func (cu *CurlJob) Response() *http.Response { | ||
cu.RLock() | ||
defer cu.RUnlock() | ||
return cu.response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the response here is still mutable: the mutex only prevents it from being set or unset when the function returns... another actor could modify the repose itself (in theory.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This http.reponse is special, for example, after we read the http body, we can no read it again.
I think we can ignore this problem. 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the getter method has been added to eliminate data race during concurrent read/write operations.
funcJob1 := quartz.NewFunctionJob(func(_ context.Context) (string, error) { | ||
n += 2 | ||
atomic.AddInt32(&n, 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be inclined to use the atomic.Int64
type/wrapper which ends up being clearer/easier...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. But it is available starting from go1.19.
I guess I don't see where, exactly the race was being triggered from the change? Something accessing the body of the job? I often try and fix races by avoiding passing pointers when you don't need mutability, rather than adding mutexes (and I try and avoid RW mutexes except as a last resort; these days)... There are a lot of orthogonal changes scattered around this PR, while I'm a bit confused by it, I'm sure it's probably fine. Was there something in particular you wanted my feedback on? |
@tychoish, thanks for looking into this.
|
I mean, callbacks are cool, though I think given that we're letting people execute arbitrary code (e.g. everything could be a function job, people could write functions that include post-operation-behavior, etc.) so it might not be always useful for users (but it might be useful for us, internally? (e.g. rescheduling/re-queuing could all happen in a callback?) but I actually tend to think that callbacks lead to harder to read code.
I agree that we shouldn't use atomic.Value here, sync.Mutex is probably fine (really), though I think at this point the go1.18 limitation is a bit aggressive. Maybe fix the bug, do a release, then bump the go version and have the next release make some of these changes more reasonably?
Oh, I think there are two different ways to fix data races: use concurrency control (mutexes/etc.) to limit access, and "make copies of data and pass them to each actor in the system and deal with the fact that actors might have different views of the system" (and one way you can do that is avoid the use of pointers so that go's dispatcher just makes the copies (probably on the stack) for you. |
Is this okay? 😁
|
@rfyiamcool, thank you. This eliminates potential data races. I've implemented it as: func (cu *CurlJob) DumpResponse(body bool) ([]byte, error) {
cu.Lock()
defer cu.Unlock()
return httputil.DumpResponse(cu.response, body)
} |
The PR discussion is here - (#77).