Re: code review 4978047: ld: Fixes issue 1899 ("cannot create 8.out.exe") (issue 4978047)

42 views
Skip to first unread message

alex.b...@gmail.com

unread,
Sep 15, 2011, 3:38:12 AM9/15/11
to j...@webmaster.ms, r...@golang.org, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On 2011/09/14 15:19:45, rsc wrote:
> LGTM


Can I have more time to test this, please?

Alex

http://codereview.appspot.com/4978047/

Russ Cox

unread,
Sep 15, 2011, 11:21:35 AM9/15/11
to j...@webmaster.ms, r...@golang.org, alex.b...@gmail.com, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On Thu, Sep 15, 2011 at 03:38, <alex.b...@gmail.com> wrote:
> Can I have more time to test this, please?

Sure, it's all yours to submit or not.

alex.b...@gmail.com

unread,
Sep 15, 2011, 10:27:51 PM9/15/11
to j...@webmaster.ms, r...@golang.org, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
Thank you for sticking with this.


http://codereview.appspot.com/4978047/diff/11011/doc/progs/run
File doc/progs/run (right):

http://codereview.appspot.com/4978047/diff/11011/doc/progs/run#newcode46
doc/progs/run:46: TMPFILE="/tmp/gotest3-$$-$USER"
I didn't look closely at this. Considering that main problem with 8l is
not resolved (see my other comment), I am not sure if this change helps
any or not.

The only thing that I have noticed is that sometimes this script will
leave files in /tmp, because of "set -e". If you proceed with this
change, perhaps it is OK just hard code file name. And it does not need
to be in /tmp. Everything in here runs in sequence.

http://codereview.appspot.com/4978047/diff/11011/src/cmd/ld/lib.c
File src/cmd/ld/lib.c (right):

http://codereview.appspot.com/4978047/diff/11011/src/cmd/ld/lib.c#newcode78
src/cmd/ld/lib.c:78: #endif
I wish it would work, but it doesn't. This program:

package main

import (
"bytes"
"exec"
"io"
"io/ioutil"
"log"
"os"
"strconv"
)

const prog = `
package main
func main() {
println("Hello")
}
`

/*
func runOne(args ...string) {
cmd := args[0]
args = args[1:]
b, err := exec.Command(cmd, args...).CombinedOutput()
if err != nil {
log.Fatalf("%s failed: %s: %s\n", cmd, err, b)
}
}
*/

func runOne(args ...string) string {
cmd := args[0]
fullcmd, err := exec.LookPath(cmd)
if err != nil {
log.Fatalf("LookPath(%s): %v", cmd, err)
}

r, w, err := os.Pipe()
if err != nil {
log.Fatalf("Pipe: %v", err)
}
attr := &os.ProcAttr{Files: []*os.File{nil, w, w}}
p, err := os.StartProcess(fullcmd, args, attr)
if err != nil {
log.Fatalf("StartProcess: %v", err)
}
defer p.Release()
w.Close()

var b bytes.Buffer
io.Copy(&b, r)
output := b.String()
msg, err := p.Wait(0)
if err != nil {
log.Fatalf("Wait: %v", err)
}
if !msg.Exited() || msg.ExitStatus() != 0 {
log.Fatalf("ExitStatus(%d): %v", msg.ExitStatus(), output)
}
return output
}

func run() {
// create source file
err := ioutil.WriteFile("hello.go", []byte(prog), 0666)
if err != nil {
log.Fatal(err)
}
defer os.Remove("hello.go")
// compile source file
runOne("8g", "-o", "hello.8", "hello.go")
defer os.Remove("hello.8")
// link executable
runOne("8l", "-o", "hello.exe", "hello.8")
// defer os.Remove("hello.exe")
// run executable
runOne("./hello.exe")
}

func main() {
if len(os.Args) != 2 {
log.Fatal("Invalid numberof args")
}
n, e := strconv.Atoi(os.Args[1])
if e != nil {
log.Fatalf("Must be a number (%s): %s\n", os.Args[1], e)
}
for i := 0; i < n; i++ {
run()
}
}

fails if I run it like

test.exe 10000

I wrote this, so we can't blame mingw or anything.

Your version improves things a bit (it doesn't fail as often as
original), but it is not 100%. Your previous attempts (where you were
moving file before deletion) do not work 100% either.

I tend to lean towards submitting this change - at least it improves on
our current situation. Maybe make a comment to say it is not 100%. What
do you think?

http://codereview.appspot.com/4978047/

j...@webmaster.ms

unread,
Sep 15, 2011, 10:38:29 PM9/15/11
to r...@golang.org, alex.b...@gmail.com, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On 2011/09/16 02:27:50, brainman wrote:


http://codereview.appspot.com/4978047/diff/11011/doc/progs/run#newcode46
> doc/progs/run:46: TMPFILE="/tmp/gotest3-$$-$USER"
> I didn't look closely at this. Considering that main problem with 8l
is not
> resolved (see my other comment), I am not sure if this change helps
any or not.

> The only thing that I have noticed is that sometimes this script will
leave
> files in /tmp, because of "set -e". If you proceed with this change,
perhaps it
> is OK just hard code file name. And it does not need to be in /tmp.
Everything
> in here runs in sequence.

I copied the tmp file code from test/run. Well, there was no set -e.

> fails if I run it like

> test.exe 10000

> I wrote this, so we can't blame mingw or anything.

Two questions:
1. does test/run fail on your machine ?
2. does this program fail if you do not provide pipe for stdout and
stderr (try nil for all 3 or handle of a disk file) ?

http://codereview.appspot.com/4978047/

alex.b...@gmail.com

unread,
Sep 15, 2011, 10:51:56 PM9/15/11
to j...@webmaster.ms, r...@golang.org, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On 2011/09/16 02:38:29, jp wrote:
> 1. does test/run fail on your machine ?

I don't know. I didn't run it. I did see it fail in a similar fashion on
a different PC of mine couple month ago.

> 2. does this program fail if you do not provide pipe for stdout and
stderr (try

> nil for all 3 or handle ...) ?

Tried that. It still fails. Does my program fails for you?

Alex

http://codereview.appspot.com/4978047/

j...@webmaster.ms

unread,
Sep 15, 2011, 11:01:12 PM9/15/11
to r...@golang.org, alex.b...@gmail.com, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On 2011/09/16 02:51:56, brainman wrote:
> On 2011/09/16 02:38:29, jp wrote:
> > 1. does test/run fail on your machine ?

> I don't know. I didn't run it. I did see it fail in a similar fashion
on a
> different PC of mine couple month ago.

> > 2. does this program fail if you do not provide pipe for stdout and
stderr
> (try
> > nil for all 3 or handle ...) ?

> Tried that. It still fails. Does my program fails for you?

No, and neither bash.exe.
I could reproduce the bug with doc/progs/run putting go directory to
external usb drive (I do not have it right now to check your new prog
with it).
But I could not reproduce the bug with test/run tests.
So I started comparing doc/progs/run and test/run and after changing the
method of capturing output of test program all tests from doc/progs/run
passed many times even on that usb drive.


http://codereview.appspot.com/4978047/

alex.b...@gmail.com

unread,
Sep 18, 2011, 11:49:16 PM9/18/11
to j...@webmaster.ms, r...@golang.org, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
I see no way to improve on your change to ld.

Please fix problem with run script (where it would leave tmp file behind
on error), and I will submit. Perhaps, if you just use a fixed name tmp
file in $GOROOT/doc/progs directory, that will do the trick.

Thank you.

Alex

http://codereview.appspot.com/4978047/

Hector Chu

unread,
Sep 19, 2011, 4:02:21 AM9/19/11
to golang-dev
Has anyone thought of trying NtDeleteFile? I'll have a go tonight.

j...@webmaster.ms

unread,
Sep 19, 2011, 8:19:44 AM9/19/11
to golan...@googlegroups.com


понедельник, 19 сентября 2011 г. 12:02:21 UTC+4 пользователь Hector Chu написал:
Has anyone thought of trying NtDeleteFile?  I'll have a go tonight.


Sounds interesting.
Perhaps it would have sense to implement also syscall.Remove() with NtDeleteFile() to be closer to Unix semantic.

Hector Chu

unread,
Sep 19, 2011, 7:00:55 PM9/19/11
to golang-dev
According to my tests using NtDeleteFile instead of DeleteFile did not
appear to help any :-( .

brainman

unread,
Sep 19, 2011, 7:19:14 PM9/19/11
to golan...@googlegroups.com
I see different stories from different people about this issue. Please tell us what do you see. Thank you.

Alex

Hector Chu

unread,
Sep 20, 2011, 4:01:28 AM9/20/11
to golang-dev
Well I thought NtDeleteFile had the unix delete semantics so I tried
it instead of remove() but it still has the same underlying problem -
it merely queues an io request packet with the delete bit set, so
deletion is still asynchronous with no way to wait for its
completion. I read elsewhere that the best way to simulate unix
delete is to rename the file being deleted to a temporary directory
first, and then call remove() on it. GetTempFileName() together with
GetTempPath() would be used to generate the temporary filename.

brainman

unread,
Sep 21, 2011, 3:37:12 AM9/21/11
to golan...@googlegroups.com
Looks like this is working for me. I will test more. Can you try it too. I wonder what happens if TEMP directory and our executable are on different drives.

Alex


package main

import (
    "bytes"
    "exec"
    "io"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strconv"
func buildAndRun() {

    const prog = `
package main
func main() {
println("Hello")
}
`
    // create source file
    err := ioutil.WriteFile("hello.go", []byte(prog), 0666)
    if err != nil {
        log.Fatal(err)
    }
    defer os.Remove("hello.go")
    // compile source file
    runOne("8g", "-o", "hello.8", "hello.go")
    defer os.Remove("hello.8")
    // link executable
    runOne("8l", "-o", "hello.exe", "hello.8")
//    defer os.Remove("hello.exe")
    // run executable
    runOne("./hello.exe")
}

func _____removeFile(name string) {
    err := os.Remove(name)
    if err != nil {
        if e, ok := err.(*os.PathError); ok && e.Error == os.ENOENT {
            return
        }
        log.Fatalf("Remove(%s): %v", name, err)
    }
    return
}

func removeFile(name string) {
    f, err := ioutil.TempFile("", "")
    if err != nil {
        log.Fatalf("TempFile(): %v", err)
    }
    tmpname := f.Name()
    f.Close()
    err = os.Remove(tmpname)
    if err != nil {
        log.Fatalf("Remove1(%s): %v", tmpname, err)
    }
    err = os.Rename(name, tmpname)
    if err != nil {
        if e, ok := err.(*os.LinkError); ok && e.Error == os.ENOENT {
            return
        }
        log.Fatalf("Rename(%s, %s): %v", name, tmpname, err)
    }
    err = os.Remove(tmpname)
    if err != nil {
        log.Fatalf("Remove2(%s): %v", tmpname, err)
    }
    return
}

func copyAndRun() {
    const cmd = "ipconfig"
    exefile := filepath.Join(os.TempDir(), "tmp.exe")

    fullcmd, err := exec.LookPath(cmd)
    if err != nil {
        log.Fatalf("LookPath(%s): %v", cmd, err)
    }
    removeFile(exefile)
    f, err := os.Create(exefile)
    if err != nil {
        log.Fatalf("Create(%s): %v", exefile, err)
    }
    buf, err := ioutil.ReadFile(fullcmd)
    if err != nil {
        log.Fatalf("ReadFile(%s): %v", fullcmd, err)
    }
    _, err = f.Write(buf)
    if err != nil {
        log.Fatalf("Write(%s): %v", exefile, err)
    }
    f.Close()
    runOne(exefile)

}

func main() {
    if len(os.Args) != 2 {
        log.Fatal("Invalid numberof args")
    }
    n, e := strconv.Atoi(os.Args[1])
    if e != nil {
        log.Fatalf("Must be a number (%s): %s\n", os.Args[1], e)
    }
    for i := 0; i < n; i++ {
//        buildAndRun()
        copyAndRun()
    }
}

Hector Chu

unread,
Sep 21, 2011, 6:17:41 AM9/21/11
to golang-dev
On Sep 21, 8:37 am, brainman <alex.brain...@gmail.com> wrote:
> Looks like this is working for me. I will test more. Can you try it too.

Works for me too.

> I wonder what happens if TEMP directory and our executable are on different
> drives.

Good point. In that case let's not bother renaming it to temp
directory, just rename to temp name in current directory.

This workaround could be implemented by defining a new function
w32remove() in lib9, and #defining remove to w32remove in include/
libc.h. Also are you planning on implementing the same workaround in
syscall.Unlink?

brainman

unread,
Sep 21, 2011, 9:00:37 AM9/21/11
to golan...@googlegroups.com
On Wednesday, 21 September 2011 20:17:41 UTC+10, Hector Chu wrote:
Good point.  In that case let's not bother renaming it to temp
directory, just rename to temp name in current directory.

SGTM
 
This workaround could be implemented by defining a new function
w32remove() in lib9, and #defining remove to w32remove in include/
libc.h.

Are you sure, you want to change all remove functions? Here our hand is forced, we have no choice. I am not so sure it is a good idea to change the others.
 
 Also are you planning on implementing the same workaround in
syscall.Unlink?

Again. Why not leave it the way it is?

If you would like to implement it, go ahead. If not, I'll try and do it myself. I'll be away next week, so I will not get around to doing it until after I come back.

Alex

Russ Cox

unread,
Sep 21, 2011, 9:32:10 AM9/21/11
to golan...@googlegroups.com
It seems likely to me that Hector is right and that
our hand will be forced eventually in the Go library
too, but I don't mind waiting until the need arises.

Russ

Hector Chu

unread,
Sep 21, 2011, 10:25:01 AM9/21/11
to golang-dev
On Sep 21, 2:00 pm, brainman <alex.brain...@gmail.com> wrote:
> Are you sure, you want to change all remove functions? Here our hand is
> forced, we have no choice. I am not so sure it is a good idea to change the
> others.

Sure, why not? There may be other places or future code that assumes
unix delete semantics, and we can avoid sprinkling ifdefs around.

> If you would like to implement it, go ahead. If not, I'll try and do it
> myself. I'll be away next week, so I will not get around to doing it until
> after I come back.

That's ok, we'll wait till you're back. Even if I did it it would
probably have to be reviewed by you anyway.

j...@webmaster.ms

unread,
Sep 23, 2011, 7:56:05 PM9/23/11
to r...@golang.org, alex.b...@gmail.com, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On 2011/09/19 03:49:15, brainman wrote:
PTAL

alex.b...@gmail.com

unread,
Sep 23, 2011, 8:32:10 PM9/23/11
to j...@webmaster.ms, r...@golang.org, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On 2011/09/23 23:56:05, jp wrote:
> On 2011/09/19 03:49:15, brainman wrote:
> PTAL

Please, see this conversation http://goo.gl/RGFp6. Hector suggested to
rename file into a *temp* name, and it looks like it works for both him
and me. His suggestion is similar to one of your attempts, except the
temp file we choose is completely unique every time we use it.

Please, check if some programs I have posted work for you, then we could
implement it. I will be away for a week, so you could have a go, if you
like.

Alex

http://codereview.appspot.com/4978047/

j...@webmaster.ms

unread,
Sep 23, 2011, 9:06:01 PM9/23/11
to golan...@googlegroups.com
вторник, 20 сентября 2011 г. 12:01:28 UTC+4 пользователь Hector Chu написал:

  I read elsewhere that the best way to simulate unix
delete is to rename the file being deleted to a temporary directory
first, and then call remove() on it.  GetTempFileName() together with
GetTempPath() would be used to generate the temporary filename.

There is one disadvantage: renaming of a locked file will work well ONLY if the source and destination are on the same disk drive.
Otherwise what rename() will do is copy file then remove. 
copy will succeed, remove will fail.

That is why I renamed file to sprintf("%s~", oldname), to stay within the same directory.

The directory returned by GetTempFileName() can be on the another disk drive.
 

brainman

unread,
Sep 23, 2011, 9:10:21 PM9/23/11
to golan...@googlegroups.com
On Saturday, 24 September 2011 11:06:01 UTC+10, j...@webmaster.ms wrote:

There is one disadvantage: ...

I think you have missed this post from Hector:

> I wonder what happens if TEMP directory and our executable are on different 
> drives. 

Good point.  In that case let's not bother renaming it to temp 
directory, just rename to temp name in current directory. 

This workaround could be implemented by defining a new function 
w32remove() in lib9, and #defining remove to w32remove in include/ 
libc.h.  Also are you planning on implementing the same workaround in 
syscall.Unlink?

j...@webmaster.ms

unread,
Sep 23, 2011, 9:18:10 PM9/23/11
to golan...@googlegroups.com


> I wonder what happens if TEMP directory and our executable are on different
> drives.

Good point.  In that case let's not bother renaming it to temp
directory, just rename to temp name in current directory.

We had this solution.
The problem is:
1. if we rename always to sprintf("%s~", oldname), we just postpone the problem with deletion of 8.out.exe to the same problem with deletion of 8.out.exe~
2. if we rename to a random name, eventually we will have garbage files in a working directory. Well, there is MoveFileEx and MOVEFILE_DELAY_UNTIL_REBOOT, but until reboot there will be files which the linker failed to delete: 8.out.exe~1, 8.out.exe~2, 8.out.exe~3

hect...@gmail.com

unread,
Sep 24, 2011, 8:18:57 AM9/24/11
to j...@webmaster.ms, r...@golang.org, alex.b...@gmail.com, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
I think we need to clarify what we want to work and what we shouldn't
support.

We would like the following to work:
- Overwrite an existing binary that is not running or has just finished
running.

We shouldn't/can't support the following:
- Overwrite an existing binary that is currently running.
- Creating a new binary with a name that is pending deletion.

On that basis I think jp's changes LGTM.

NB. The issue with bash is probably because command substitution doesn't
wait for the command to complete, it just waits for the pipe to close.
The former doesn't necessarily happen before the latter.

http://codereview.appspot.com/4978047/

alex.b...@gmail.com

unread,
Sep 24, 2011, 9:08:13 AM9/24/11
to j...@webmaster.ms, r...@golang.org, bsie...@gmail.com, hect...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
On 2011/09/24 12:18:57, hector wrote:

> I think we need to clarify what we want to work and what we shouldn't
support.


Whatever you say, but current change (removeing "remove" before
"create") does not fixes the issue - I have a computer that still fails,
if you test it for long enough.

On the other hand, if we move file before deleting it, I could run test
for very long time and it would not fail. I was monitoring all
operations and none of them failed. It means we should not have garbage
files.

I am OK with whatever you decide to do. But I will keep with this myself
when I am back.

Alex

http://codereview.appspot.com/4978047/

hect...@gmail.com

unread,
Sep 24, 2011, 10:26:03 AM9/24/11
to j...@webmaster.ms, r...@golang.org, alex.b...@gmail.com, bsie...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com

> Alex

Yep, it looks like an impossible problem.

There are really 2 problems, which combined prevent us from fixing the
issue:
1. It is impossible to overwrite or delete a binary that is running.
2. 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.

As jp has said renaming isn't good enough as it's impossible to delete a
running binary, whether it has been renamed or not.

I think the only sane thing to do now is to revert the change to
ld/lib.c and change doc/progs/run so that the output file has the same
base name as the source file. Except that I see cat_rot13 is run twice,
so provisions will have to be made so that it doesn't get built twice.

http://codereview.appspot.com/4978047/

j...@webmaster.ms

unread,
Sep 24, 2011, 3:12:38 PM9/24/11
to r...@golang.org, alex.b...@gmail.com, bsie...@gmail.com, hect...@gmail.com, golan...@googlegroups.com, re...@codereview.appspotmail.com
> I think the only sane thing to do now is to revert the change to
ld/lib.c and change doc/progs/run so that the output file has the same
base name as the source file. Except that I see cat_rot13 is run twice,
so provisions will have to be made so that it doesn't get built twice.

There are also tests in $GOROOT/test directory, not only in
$GOROOT/doc/progs/run.
If we revert changes in ld/lib.c they will fail.
There are a lot of them and some of them made such a way that one Go
program emit source for another one. And both of them are compiled to
the same binary 8.out.exe with no delay.
It would be a nice solution to give unique filename to each test (and
unique filename to each binary within the test), but it would be a much
bigger change than only can_rot13.

http://codereview.appspot.com/4978047/

Reply all
Reply to author
Forward
0 new messages