-
Notifications
You must be signed in to change notification settings - Fork 0
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
Milestone 1 - Core Functionality #1
Conversation
…ulated after the command exits.
…re" normal ProcStatus.
0ecb392
to
7aeaa6d
Compare
proc/proc.go
Outdated
case <-ticker.C: | ||
// ReadLoop | ||
for { | ||
b, err := ioutil.ReadAll(bufFile) |
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 might not be a good idea to put the whole file in memory
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.
Hahaha, yes. I think I rushed this implementation over from some quick testing I did. I'll clean that up. Does reading till new lines make sense to you or would you rather see a fixed number of bytes? (I lean towards #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.
yes fixed number bytes is the way to go 👍
proc/proc.go
Outdated
// Proc is a BitBox process. | ||
// Stderr and Stdout are dumped to temp files on disk. | ||
type Proc struct { | ||
// TODO: there is 99% chance we need a synchronization primitive at this level |
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.
can you explain why we might/not need the sync primitive?
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.
Late nights make concurrent code extra hard. Let me walk through my thoughts and see what it leads to. I'm not certain about either direction yet.
My thoughts were that __FileNames are only ever written during NewProc where concurrent access isn't yet possible. After that cmd could have:
- Concurrent calls to Wait()
- Reads of cmd.ProcessState
- Reads of cmd.process for os.Process.Kill()
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.
- Concurrent calls to Wait()
I am certain we need a synchronization primitive because of lines 502-505 in the definition of cmd.Wait() in os/exec/exec.go
If a routine is preempted after if c.finished {
but before c.finished = true
two routines would attempt to visit the resource clean up section of the function.
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.
- Reads of cmd.ProcessState
I think we should be good here. ProcessState
is a pointer that should be set with a single instruction during Wait()
so it should be safe for concurrent access.
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.
- Reads of cmd.process for os.Process.Kill()
I think a mutex is definitely needed to protect cmd.Wait()
, but if cmd.process.Kill()
is protected by the same mutex the process will never be able to be killed before it has exited. With that said, I think cmd.process.Kill()
is safe for concurrent calls because the method makes several naked calls until it reaches Process.signal()
in os/exec_unix.go where its protected by a lock on line 69. So I think we are good there.
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.
Okay. I've protected cmd.Wait
with a mutex. Anything else you feel is pending here @NajiObeid?
proc/proc.go
Outdated
if err := pollRead(ctx, p.stdoutFileName, wg, stream, newProcOutput_Stdout); err != nil { | ||
cancel() | ||
return nil, fmt.Errorf("failed to setup poll read on stdout: %w", err) | ||
} | ||
if err := pollRead(ctx, p.stderrFileName, wg, stream, newProcOutput_Stderr); err != nil { | ||
cancel() | ||
return nil, fmt.Errorf("failed to setup poll read on stderr: %w", err) | ||
} |
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 comes hand in hand with my previous comment about joining both outputs
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, I think you've made an excellent point! I responded to you there.
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 may have been covered by Naji's remarks: In the Query
logic I'm concerned about termination/leaks in the case of a failure to read everything from the returned <-chan ProcOutput
. This is the the "client reads from second 2-5 of 10s of output" case discussed earlier. It looks like the code will load data into memory, and then block forever on the send, if nothing nothing receives on the far end. This seems like a risk of both a goroutine & memory leak -- if I understand the logic correctly.
proc/proc.go
Outdated
if p.cmd.ProcessState.ExitCode() != 0 { | ||
return Stopped | ||
} | ||
return Exited |
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.
Based on your comments below, this logic doesn't look correct to me. The docs say:
ExitCode returns the exit code of the exited process, or -1 if the process hasn't exited or was terminated by a signal.
https://golang.org/pkg/os/#ProcessState.ExitCode
Whereas you say:
// Stopped indicates that the process returned no exit code.
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.
Great catch. It took me a minute to realize where my brain was hung up here. I changed the definitions of Stopped and Exited and the check to be in line with ExitCode()
because that made the most sense to me. How does it look to you now?
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.
LGTM.
proc/proc.go
Outdated
// Proc is a BitBox process. | ||
// Stderr and Stdout are dumped to temp files on disk. | ||
type Proc struct { | ||
// TODO: there is 99% chance we need a synchronization primitive at this level |
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.
Late nights make concurrent code extra hard. Let me walk through my thoughts and see what it leads to. I'm not certain about either direction yet.
My thoughts were that __FileNames are only ever written during NewProc where concurrent access isn't yet possible. After that cmd could have:
- Concurrent calls to Wait()
- Reads of cmd.ProcessState
- Reads of cmd.process for os.Process.Kill()
proc/proc.go
Outdated
// Proc is a BitBox process. | ||
// Stderr and Stdout are dumped to temp files on disk. | ||
type Proc struct { | ||
// TODO: there is 99% chance we need a synchronization primitive at this level |
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.
- Concurrent calls to Wait()
I am certain we need a synchronization primitive because of lines 502-505 in the definition of cmd.Wait() in os/exec/exec.go
If a routine is preempted after if c.finished {
but before c.finished = true
two routines would attempt to visit the resource clean up section of the function.
proc/proc.go
Outdated
if err := pollRead(ctx, p.stdoutFileName, wg, stream, newProcOutput_Stdout); err != nil { | ||
cancel() | ||
return nil, fmt.Errorf("failed to setup poll read on stdout: %w", err) | ||
} | ||
if err := pollRead(ctx, p.stderrFileName, wg, stream, newProcOutput_Stderr); err != nil { | ||
cancel() | ||
return nil, fmt.Errorf("failed to setup poll read on stderr: %w", err) | ||
} |
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, I think you've made an excellent point! I responded to you there.
proc/proc.go
Outdated
case <-ticker.C: | ||
// ReadLoop | ||
for { | ||
b, err := ioutil.ReadAll(bufFile) |
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.
Hahaha, yes. I think I rushed this implementation over from some quick testing I did. I'll clean that up. Does reading till new lines make sense to you or would you rather see a fixed number of bytes? (I lean towards #2)
7092f5d
to
e456811
Compare
60f9885
to
4e32788
Compare
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 there are still some areas for improvement (notably concurrency handling in query), but I think integrating this logic with a grpc server and client will help bring them to light. I'd like to move forward to that point, so we have a bit more holistic view of the system.
Add
bitbox.Core
Offers process manipulation and output based on uuids.
Add
proc.Proc
Wrapper around os/exec.Cmd which offers