-
Notifications
You must be signed in to change notification settings - Fork 1k
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed, please reply here (e.g.
|
log/logger.go
Outdated
} | ||
|
||
// LogDepfln logs a formatted line, prefixed with `dep: `. | ||
func (l *Logger) LogDepfln(format string, args ...interface{}) { |
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 method could use a better name. Or we could just inline the format adjustments and call Logf
. There are 13 usages.
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 minimal logger is racy, so I'm going to try dissolving this logger package completely and using the std lib log.Logger
instead, so this helper will move or be in-lined.
bump googlebot: I signed it! |
CLAs look good, thanks! |
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.
just some quick comments while you're on it
cmd/dep/integration_test.go
Outdated
testProj.CompareImportPaths() | ||
testCase.CompareVendorPaths(testProj.GetVendorPaths()) | ||
t.Run("-external", testIntegration(testName, wd, execCmd)) | ||
t.Run("-internal", testIntegration(testName, wd, runMain)) |
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.
no dash needed, the system automatically separates with a slash
cmd/dep/remove_test.go
Outdated
t.Fatal("rm with one arg not in manifest should have failed") | ||
} | ||
t.Run(testName+"-external", removeErrors(testName, wd, execCmd)) | ||
t.Run(testName+"-internal", removeErrors(testName, wd, runMain)) |
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.
ibid - no need for dash
cmd/dep/status.go
Outdated
o string | ||
g *graphviz | ||
p *dep.Project | ||
logger *log.Logger |
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 think it's probably preferable to have this remain as an io.Writer
, then map that to the *log.Logger
in some intermediate step.
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.
Yeah I ended up having to do that for the tabwriter anyways.
gps/source_manager.go
Outdated
@@ -216,7 +218,7 @@ func (sm *SourceMgr) HandleSignals(sigch chan os.Signal) { | |||
|
|||
opc := sm.suprvsr.count() | |||
if opc > 0 { | |||
fmt.Printf("Signal received: waiting for %v ops to complete...\n", opc) | |||
sm.logf("Signal received: waiting for %v ops to complete...\n", opc) |
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.
sorry, this one has to stay as-is. yes, it's obnoxious to print directly in this way, but the interface here is carefully designed; we're not injecting a logger for this one, narrow case. if it's that bad, we'll just delete this logging output entirely.
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.
It would clean up a lot to remove it, but how would it affect the user experience?
On the other hand, is there a chance that leaving it to log as before could result in arbitrary test failures?
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.
No, there's no chance of it bunking up tests; the only possible way for it to occur is if a signal is sent. It's a very specific case, which is why it exists as it does in the first place.
The proper solution here involves moving direct signal handling out of gps, and into the tool-level code - not changing gps' interface for this sole logging statement.
gps/solver.go
Outdated
@@ -105,12 +105,9 @@ type SolveParameters struct { | |||
// typical case. | |||
Downgrade bool | |||
|
|||
// Trace controls whether the solver will generate informative trace output | |||
// as it moves through the solving process. | |||
Trace bool |
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.
Let's just leave everything underneath gps/ out of this refactor, unless it absolutely must change.
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 can put this back, but it's not doing anything, and I suspect it will confuse others in the future as it did me. (Maybe a separate issue if it's just clouding the water on this PR)
If it was set to true
, then there was a validation check for a non-nil TraceLogger
(even though nil checking the logger is the actual mechanism that triggers the trace logging internally).
If it was set to false, then it wasn't used. So you could pass a non-nil logger, and it would be passed along and tracing will take place, in spite of the false
trace value.
So in it's current state it really amounts to "double check that TraceLogger
is non-nil", but the only usages are setting a constant true
value directly adjacent a call to set TraceLogger
non-nil.
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.
Yep, I know that it looks like it amounts to a double non-nil check, and it may actually be that (I vaguely recall, from when I wrote it a year ago, there being an actual reason for having the extra bool). But that needs its own PR - as you said, the issue is about muddying the water.
cmd/dep/integration_test.go
Outdated
@@ -35,8 +35,6 @@ func TestIntegration(t *testing.T) { | |||
parse := strings.Split(path, string(filepath.Separator)) | |||
testName := strings.Join(parse[2:len(parse)-1], "/") | |||
t.Run(testName, func(t *testing.T) { | |||
t.Parallel() |
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.
It's a major speedup to run these tests in parallel; there'd need to be a justification for dropping back to running them in serial.
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.
Yeah this was an attempt to mitigate the race popping up on the integration test buffers. Definitely trying to keep this in.
There are still currently race conditions blocking re-parallelization of the integration tests. It appears to be orphaned goroutines continuing to log after the initial call has returned, which explains why the external-process tests are not affected. It may be possible to quick-fix by swapping out the logger when returning, so any lingering logging will go to |
Sometimes, but not always, the osx tests fail: https://travis-ci.org/golang/dep/jobs/229474199 |
Resolved the race conditions with a few Resolved the OSX issue as well - the existing hack needed only be applied to the external process tests. |
awesome, thanks for keeping at this! 🎉 🎉 i'm working through a review. meantime, to this:
If gps' internals are working correctly, then these should all be cleaned up/terminated by calling A note - the current philosophy of test design is, when the SUT is not the |
I don't think that what you are describing is fundamentally a problem. I was primarily concerned with reliance on the process exiting to terminate background goroutines (either by design, or from a race). I think that some of these calls, like firing off an un-monitored |
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 looking pretty good - still chewing on it a little, but here are some comments while i do 😄
gps/result_test.go
Outdated
@@ -104,6 +110,8 @@ func testWriteDepTree(t *testing.T) { | |||
if _, err = os.Stat(filepath.Join(tmp, "bitbucket.org", "sdboyer", "withbm")); err != nil { | |||
t.Errorf("Directory for bitbucket.org/sdboyer/withbm does not exist") | |||
} | |||
|
|||
wg.Wait() |
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.
Mostly for my curiosity - was this causing an actual problem, or is the change here prophylactic?
The implementation of WriteDepTree()
should entail that all of the SyncSourceFor()
goroutines finish before WriteDepTree()
does. It's possible that they wouldn't if one of the items encountered an error (and it's generally not great test design to rely on internal details of the SUT to resolve concurrency issues established by the test), but I'd definitely like to know if that entailment doesn't hold.
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.
Only the logging goroutines were causing actual race failures. I fixed this one the easy way when I started chasing these down, but I will revert it.
// this way we get the default GOPATH that was added in 1.8 | ||
buildContext := build.Default | ||
wd, err := os.Getwd() | ||
func NewContext(wd string, env []string) (*Ctx, error) { |
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'm glad you went to injecting this without me having to suggest it 😄 You went further than I'd envisioned by passing in a whole env []string
; I was imagining just GOPATH string
. But that's great - it affords us flexibility for the future, in the event that we have more env vars we want to incorporate into dep.Ctx
. Well done 😄 🎉
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.
Then my unfamiliarity with the code base worked to my advantage, because I started by targeting general parity with the exec.Cmd
API in use since I didn't know the minimum input requirements 😆
cmd/dep/integration_test.go
Outdated
|
||
func testIntegration(name, wd string, externalProc bool, run test.RunFunc) func(t *testing.T) { | ||
return func(t *testing.T) { | ||
// Set up environment |
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.
Should probably also t.Parallel()
here.
txn_writer.go
Outdated
@@ -436,43 +437,43 @@ fail: | |||
return failerr | |||
} | |||
|
|||
func (sw *SafeWriter) PrintPreparedActions() error { | |||
func (sw *SafeWriter) PrintPreparedActions(stdout *log.Logger) error { |
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.
nit: let's name the local var output
instead of stdout
. Even if we always (currently) end up attaching stdout here, I don't think we need to suggest that via the var name.
Right, this is what I was trying to address with the comment about So, as long as the source manager is released, we shouldn't be relying on process termination to end errant goroutines. Are we saying the same thing? |
Yes I think so, we were both just a bit ambiguous. I expect these tests to be fine, since no remaining cases fail the race detector, and none resemble the fixed cases. The So basically I think that this PR and general program correctness should be fine. I only suspect technical incorrectness by definition, due to the orphaned top-level routines (w/o side affects. Of course the ones that were logging as well were a problem). Perhaps a separate issue to audit these is in order - some usages look like good candidates for translation into a simpler fork-join model, and others will be removed soon (e.g. as part of the |
Ah good, I did do this when I did the latest big source manager refactor; I couldn't remember if I did or not.
Ultimately, it's important to keep in mind that these initial goroutines are only spawned as helpers, potentially achieving some up-front parallelism gains on work we happen to know the That said, what are you envisioning, and what value do you think it would add?
Ehh...I mean, yeah, if the rule is that "logic that starts a goroutine is directly responsible for ensuring its appropriate termination." Not a bad rule, though idk if it rises to the level of correctness - it seems like mostly a question of whether it's good design to delegate that responsibility to guarantees provided by
The old system, prior to the refactor, kept an atomic counter of the number of running processes, which it incremented on ingress into one of the top-level There's logic that, ostensibly, runs counter to this. In three places, we update the cache after the supervised segment ends: in This is clearly not a problem now, as the "cache" is memory-only and ephemeral - but we do plan to move to a persistent caching layer - #431. When that change happens, we could have a situation where the SourceManager releases while writing to disk. The |
oh, also, i think we're good to go here 😄 |
That's slightly stricter than I was thinking (in that I'm not worried about ownership, so much as any eventual sync/join back into the main gouroutine), but I was flipping it around and observing: If nothing is waiting for the returned values from these routines, then why is there work queued or a stack to unwind and value to return in the first place? Practically speaking, likely because the method was already written and available, but can we use a similar method that doesn't queue up any extra work, and only requests what is necessary? Or maybe the async routine can be spawned internally, rather than at the current level. (Hopefully these approaches wouldn't require a messy trade-off like a bunch of parallel helper methods, but I haven't explored) In any case, some of the current usages seem brittle, since you only have to try and log the error returned from |
I'm not sure this necessarily follows; if the effect is idempotent (as it is in this case), then there's no race. Though perhaps it doesn't apply here simply because the goroutine is indirectly joined back with the main goroutine.
To initiate work in parallel that would have been serially initiated later anyway (by the solver), with the understanding that the later serial call will pick up the same error as was unwound and returned but discarded here.
If we're comparing to another hypothetical method that's still invoked in its own goroutine, but has no return value, then the only additional work that's being queued up is returning an error value through one, maybe two stack frames.
They could, yes - such a thing is what I was referring to with "I'd be open to an argument for pushing these down into gps directly, either into the solver and/or a new Internally, the mechanism would still be pretty much the same: initiate a bunch of goroutines to do the sync work in the background so that that work is done perhaps a bit sooner (wall time) by the time the main serial thread needs that data. And it would still be implicitly supervised and managed in the same way via A possible signature for such a method would be
I think so 😄 |
I would consider idempotent actions as not-observable by definition in this case, since the observer can't distinguish them.
Yes, I'm referring strictly to the work done on the orphaned goroutines after The larger point is just that they are doing more work than they need to, which strikes me as analogous to something like leaving an unused variable hanging around - it's just something to clean up that has no effect, but otherwise can be semantically distracting or misleading. |
I'm happy to look at issues/PRs for any particular issues that you identify. My general rubric will be whether the more tailored calls/logic merit both the added code weight and (if applicable) the expanded API surface area that the user has to choose from. |
adding in-process tests
Fixes #510
This change adds in-process tests. Most of the churn is around refactoring logging away from global functions and state, and replacing various
os
package references (Stdout
,StdErr
,Getenv
,Getwd
,etc.).