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

cmd/go: go test leaves temp directory behind #2866

Closed
alexbrainman opened this issue Feb 3, 2012 · 17 comments
Closed

cmd/go: go test leaves temp directory behind #2866

alexbrainman opened this issue Feb 3, 2012 · 17 comments
Milestone

Comments

@alexbrainman
Copy link
Member

What steps will reproduce the problem?
go test runtime -cpu=1,2

What is the expected output?
contents of tmp directory is not changed.

What do you see instead?
new go-build* directory created in tmp

Please use labels and text to provide additional information.
hg id ff1def880707
windows/386
@rsc
Copy link
Contributor

rsc commented Feb 3, 2012

Comment 1:

Alex, are you volunteering to fix this?
If not (which is fine), please reassign to builder@golang.org.
Thanks.

Labels changed: added priority-go1, removed priority-triage.

@alexbrainman
Copy link
Member Author

Comment 2:

I would if I could, but I do not know why it is happening. Yet.
Alex

Owner changed to builder@golang.org.

@rsc
Copy link
Contributor

rsc commented Feb 6, 2012

Comment 3:

I can't reproduce this on Unix.  I suspect that some file cannot
be deleted (probably runtime.test.exe) and therefore the 
whole directory cannot be deleted.  Can you look in the
temp directory and see what's left?  Probably just a file or two.

@alexbrainman
Copy link
Member Author

Comment 4:

C:\...\Temp\go-build932562370\runtime\_test\runtime.test.exe is left. Nothing else.
I suspect it is a variation of https://golang.org/issue/1899.
Please, create "Too Hard" label, and we'll mark this one. :-)
Alex

@rsc
Copy link
Contributor

rsc commented Feb 9, 2012

Comment 5:

I would kind like to understand what causes this.
For example, are we not actually waiting for the
test to exit properly?  If the program has exited,
we should be able to remove the .exe.  If the
program has not exited, why are we trying?

@alexbrainman
Copy link
Member Author

Comment 6:

I am far from clear about what is happening here. I have been debugging it, but I have
no clear picture yet. I plan to continue.
As far as guessing goes, I have 2 possibilities in mind.
1) It could be variation of something we have seen before.
http://golang.org/cl/4978047
If you look at hector comment:
" ... It is impossible to know when the binary has been unmapped from memory.
Waiting on the process handle doesn't work because the handle gets signalled as
soon as ExitProcess() is called, and the binary will still be mapped at that
time. ..."
I could not find reference to that fact anywhere. But, perhaps, hector saw it happens
while debugging program.
2) Our *os.Process has Release function, that is NOP on unix, but closes process handle
on windows. Release function is set to be called by finallizer, and most of the time it
is good enough. But os/exec.Command does not call Release explicitly, and relies on
finalizer to do it. Perhaps, we delete test binary, but process handle is still opened,
because finalizer didn't run yet. Perhaps it is the cause.
These are not even theories, just hints. Perhaps, there is something else - something
much simpler.
Alex

@rsc
Copy link
Contributor

rsc commented Feb 10, 2012

Comment 7:

Thanks.
The Release thing sounds like something we should fix anyway.
Once we've finished waiting for the process and we see that it
exited, there's no point in holding onto the handle, right?
Also, are we closing both the process and the thread handles?
Russ

@alexbrainman
Copy link
Member Author

Comment 8:

We close thread handle in syscall.StartProcess there and then, since we are do not need
it for anything anyway.
Alex

@alexbrainman
Copy link
Member Author

Comment 9:

It looks like not Releasing finished process handle does not affect our ability to
delete process binary. This program runs with no error.
package main
import (
    "os/exec"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
)
func main() {
    const prog = "ipconfig"
    tmpexe := filepath.Join(os.TempDir(), "tmp.exe")
    progpath, err := exec.LookPath(prog)
    if err != nil {
        log.Fatalf("LookPath(%s): %v", prog, err)
    }
    os.Remove(tmpexe)
    f, err := os.Create(tmpexe)
    if err != nil {
        log.Fatalf("Create(%s): %v", tmpexe, err)
    }
    buf, err := ioutil.ReadFile(progpath)
    if err != nil {
        log.Fatalf("ReadFile(%s): %v", progpath, err)
    }
    _, err = f.Write(buf)
    if err != nil {
        log.Fatalf("Write(%s): %v", tmpexe, err)
    }
    f.Close()
    cmd := exec.Command(prog)
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("%s failed: %s: %s\n", prog, err, out)
    }
    err = os.Remove(tmpexe)
    if err != nil {
        log.Fatalf("Remove(%s): %v", tmpexe, err)
    }
    // keep cmd around until after os.Remove
    _ = cmd.Path
}
This is just a test. This one worked. Other variations might not.
Alex

@alexbrainman
Copy link
Member Author

Comment 10:

I tried inserting call to os/exec.Command.Process.Release into go command, because
otherwise it never gets called:
diff -r 1e3137b41422 src/cmd/go/test.go
--- a/src/cmd/go/test.go    Fri Feb 17 14:39:50 2012 +1100
+++ b/src/cmd/go/test.go    Fri Feb 17 16:34:07 2012 +1100
@@ -586,6 +586,7 @@
            fmt.Fprintf(&buf, "*** Test killed: ran too long.\n")
        }
        tick.Stop()
+       cmd.Process.Release()
    }
    out := buf.Bytes()
    t1 := time.Now()
@@ -614,7 +615,10 @@
 func (b *builder) cleanTest(a *action) error {
    run := a.deps[0]
    testDir := filepath.Join(b.work, filepath.FromSlash(run.p.ImportPath+"/_test"))
-   os.RemoveAll(testDir)
+   e := os.RemoveAll(testDir)
+   if e != nil {
+       fmt.Printf("cleanTest: %s %s\n", testDir, e)
+   }
    return nil
 }
 
$ hg id
1e3137b41422+ tip
but it does not make any difference - os.RemoveAll still fails. I can see all events in
correct order: create process, wait for it to finish, close process handle, delete /tmp,
but still no go.
Alex

@alexbrainman
Copy link
Member Author

Comment 11:

I made a mistake in https://golang.org/issue/2866?c=9 - my program is
buggy. I intended to do:
package main
import (
    "os/exec"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
)
func main() {
    log.SetFlags(0)
    const prog = "ipconfig"
    progpath, err := exec.LookPath(prog)
    if err != nil {
        log.Fatalf("LookPath(%s): %v", prog, err)
    }
    tmpexe := filepath.Join(os.TempDir(), "tmp.exe")
    os.Remove(tmpexe)
    f, err := os.Create(tmpexe)
    if err != nil {
        log.Fatalf("Create(%s): %v", tmpexe, err)
    }
    buf, err := ioutil.ReadFile(progpath)
    if err != nil {
        log.Fatalf("ReadFile(%s): %v", progpath, err)
    }
    _, err = f.Write(buf)
    if err != nil {
        log.Fatalf("Write(%s): %v", tmpexe, err)
    }
    f.Close()
    cmd := exec.Command(tmpexe)
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("%s failed: %s: %s\n", tmpexe, err, out)
    }
    err = cmd.Process.Release()
    if err != nil {
        log.Fatalf("cmd.Process.Release(%s): %v", tmpexe, err)
    }
    err = os.Remove(tmpexe)
    if err != nil {
        log.Fatalf("Remove(%s): %v", tmpexe, err)
    }
}
This program specifically closes process handle before attempting to delete binary of
just finished process. The program fails on my windows xp computer with:
Remove(C:\DOCUME~1\brainman\LOCALS~1\Temp\tmp.exe): remove
C:\DOCUME~1\brainman\LOCALS~1\Temp\tmp.exe: Access is denied.
so it is in line with originally reported issue.
On the other hand - this program runs fine on other computers windows 2000 and windows
7. I tend to agree more and more that return from syscall.WaitForSingleObject for
process handle is sometimes too early to be able to remove process binary.
Alex

@rsc
Copy link
Contributor

rsc commented Feb 24, 2012

Comment 12:

Can you try putting in a sleep for 5 seconds and then remove?  Does that work?
If the sleep works then I agree we're in trouble and can't do much.
If the sleep is not enough then I wonder if some library is still not
quite releasing a handle.
Also, after a command finishes (which you know by calling Wait or by
calling any function that wraps it, like CombinedOutput), it should
not be necessary to call cmd.Process.Release explicitly.  Wait should
do that.

@alexbrainman
Copy link
Member Author

Comment 13:

Even time.Sleep(5 * time.Millisecond) helps - the "Access is denied" message disappear
and file gets deleted successfully.
I changed os/exec to close process handle as you suggested:
http://golang.org/cl/5699081/
Alex

@rsc
Copy link
Contributor

rsc commented Feb 27, 2012

Comment 14:

Let's add a 5ms sleep then, I guess.

@rsc
Copy link
Contributor

rsc commented Mar 1, 2012

Comment 15:

Alex, can you send a CL that adds the appropriate 5ms sleep to go test?
Thanks.

@alexbrainman
Copy link
Member Author

Comment 16:

Will do. With heavy heart :-(
Alex

@alexbrainman
Copy link
Member Author

Comment 17:

This issue was closed by revision d1bd332.

Status changed to Fixed.

@rsc rsc added this to the Go1 milestone Apr 10, 2015
@rsc rsc removed the priority-go1 label Apr 10, 2015
@golang golang locked and limited conversation to collaborators Jun 24, 2016
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants