golang-evdev icon indicating copy to clipboard operation
golang-evdev copied to clipboard

Concurrency support: detect closed file descriptors

Open bartmeuris opened this issue 5 years ago • 5 comments

In order to cancel an ongoing read, I assumed is was sufficient to close the underlying file-descriptor by calling dev.File.Close() - but the ReadOne function (and I suppose the normal Read also) hangs until a new message arrives on the input device (tested with evemu).

I don't really see any other way to abort a read in progress... I've looked briefly into the code but couldn't immediately see what would be wrong, it just seems to use normal Go i/o internally?

Tested with go version go1.11.2 linux/amd64 with evemu.

bartmeuris avatar Nov 28 '18 20:11 bartmeuris

@bartmeuris Did you resolve this problem? I've been stuck on this problem for days. @gvalkov @kovetskiy Please give me a hand

Sky-Li avatar Sep 18 '20 10:09 Sky-Li

Hey there, as far as I understand, go's os.File Close() interrupts Read().

it just seems to use normal Go i/o internally

Yes, there is just plain os.Open() called on the specified device

@Sky-Li could you elaborate on what the problem you are facing exactly? (maybe some test case?)

kovetskiy avatar Sep 18 '20 12:09 kovetskiy

Hi @kovetskiy , I tried many functions to interrupts Read(), such as SetReadDeadline(), Close(), but they have no effect.

Cause the code relies on the input device, so I don't know how to provide the test case on go playground.

Here is the example, work() will keep hanging after calling the function Stop(), until I feed the barcode scanner with some codes.


func (br *BarcodeReader) work() {
	defer func() {
		if r := recover(); r != nil {
			_ = br.log.Errorf("recover in BarcodeReader work: %v", r)
		}
	}()

	for {
		select {
		case <-br.finishChan:
			break
		default:
			br.log.Debugf("Before br read.")
			events := br.read()
			br.log.Debugf("After br read.")
			continue
		}
		break
	}
	close(br.finishChan)
	br.log.Debugf("the code scanner stop working.")
}

func (br *BarcodeReader) Stop() {
	br.finishChan <- true
	if br.scanner != nil {
		br.log.Debugf("br.scanner: ", *br.scanner)
		if err := br.scanner.Release(); err != nil {
			br.err = err
			_ = br.log.Warnf("BarcodeReader cannot be release: %v", err)
			return
		}
		br.log.Debugf("After br.scanner.Release()")
		if err := br.scanner.File.SetReadDeadline(time.Now()); err != nil {
			br.err = err
			_ = br.log.Errorf("BarcodeReader cannot set read deadline: %v", err)
			return
		}
		br.log.Debugf("After br.scanner.File.SetReadDeadline()")
		if err := br.scanner.File.Close(); err != nil {
			br.err = err
			_ = br.log.Errorf("BarcodeReader cannot close file: %v", err)
			return
		}
		br.log.Debugf("After br.scanner.File.Close()")
		br.scanner = nil
	}
}


Sky-Li avatar Sep 21 '20 06:09 Sky-Li

I see, it doesn't work for me either. I also tried syscall.Close(int(device.File.Fd())) which did not help too.

Also, from the man 2 close:

   Multithreaded processes and close()
       It is probably unwise to close file descriptors while they may be in
       use by system calls in other threads in the same process.  Since a
       file descriptor may be reused, there are some obscure race conditions
       that may cause unintended side effects.

       Furthermore, consider the following scenario where two threads are
       performing operations on the same file descriptor:

       1. One thread is blocked in an I/O system call on the file
          descriptor.  For example, it is trying to write(2) to a pipe that
          is already full, or trying to read(2) from a stream socket which
          currently has no available data.

       2. Another thread closes the file descriptor.

       The behavior in this situation varies across systems.  On some
       systems, when the file descriptor is closed, the blocking system call
       returns immediately with an error.

       On Linux (and possibly some other systems), the behavior is
       different.  the blocking I/O system call holds a reference to the
       underlying open file description, and this reference keeps the
       description open until the I/O system call completes.  (See open(2)
       for a discussion of open file descriptions.)  Thus, the blocking
       system call in the first thread may successfully complete after the
       close() in the second thread.

But if you really want to get rid of hanging read, I could only recommend you trying to patch the package a bit and use epoll (syscall.EpollCtl) instead of device.File.Read as the package does.

kovetskiy avatar Sep 21 '20 07:09 kovetskiy

I haven't tried epoll before, I will consider and try to play with it. Anyway, thank you for the explanation and the suggestion @kovetskiy.

Sky-Li avatar Sep 21 '20 08:09 Sky-Li