Skip to content
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

Merged
merged 26 commits into from
Mar 23, 2021
Merged

Conversation

jmbarzee
Copy link
Owner

Add bitbox.Core
Offers process manipulation and output based on uuids.

Add proc.Proc
Wrapper around os/exec.Cmd which offers

  1. process control
  2. Routes process output to temp files.
  3. Offers streams of output by file polling until the process has closed.

@jmbarzee jmbarzee force-pushed the milestone/1-core-functionality branch from 0ecb392 to 7aeaa6d Compare March 19, 2021 08:43
proc/proc.go Outdated Show resolved Hide resolved
core.go Outdated Show resolved Hide resolved
proc/proc.go Outdated
case <-ticker.C:
// ReadLoop
for {
b, err := ioutil.ReadAll(bufFile)

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

Copy link
Owner Author

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)

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 Show resolved Hide resolved
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

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?

Copy link
Owner Author

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:

  1. Concurrent calls to Wait()
  2. Reads of cmd.ProcessState
  3. Reads of cmd.process for os.Process.Kill()

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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.

Copy link
Owner Author

@jmbarzee jmbarzee Mar 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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.

Copy link
Owner Author

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 Show resolved Hide resolved
proc/proc.go Outdated
Comment on lines 127 to 134
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)
}

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

Copy link
Owner Author

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.

Copy link

@wadells wadells left a 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 Show resolved Hide resolved
proc/proc.go Outdated
Comment on lines 93 to 96
if p.cmd.ProcessState.ExitCode() != 0 {
return Stopped
}
return Exited
Copy link

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.

Copy link
Owner Author

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?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

core.go Outdated Show resolved Hide resolved
proc/proc.go Outdated Show resolved Hide resolved
core.go Outdated Show resolved Hide resolved
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
Copy link
Owner Author

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:

  1. Concurrent calls to Wait()
  2. Reads of cmd.ProcessState
  3. Reads of cmd.process for os.Process.Kill()

proc/proc.go Outdated Show resolved Hide resolved
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
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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
Comment on lines 127 to 134
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)
}
Copy link
Owner Author

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 Show resolved Hide resolved
proc/proc.go Outdated Show resolved Hide resolved
proc/proc.go Outdated
case <-ticker.C:
// ReadLoop
for {
b, err := ioutil.ReadAll(bufFile)
Copy link
Owner Author

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)

core.go Outdated Show resolved Hide resolved
@jmbarzee jmbarzee force-pushed the milestone/1-core-functionality branch from 7092f5d to e456811 Compare March 23, 2021 18:35
@jmbarzee jmbarzee force-pushed the milestone/1-core-functionality branch from 60f9885 to 4e32788 Compare March 23, 2021 18:46
Copy link

@wadells wadells left a 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.

@jmbarzee jmbarzee merged commit 0c05535 into main Mar 23, 2021
@jmbarzee jmbarzee deleted the milestone/1-core-functionality branch March 23, 2021 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants