Follow-up from the 1.4.2 review (#44, fix commit ec43809, verified at 53308c9).
UhidChannel.stopReader() (app/src/main/java/com/inputleaf/android/shizuku/uhid/UhidChannel.kt:149-158) cannot actually wake a reader blocked in read() on /dev/uhid:
Thread.interrupt() does not interrupt a blocking FileInputStream.read (only InterruptibleChannel I/O responds to interrupts).
- The reader's
ParcelFileDescriptor is a dup() of the same fd, which shares the kernel open file description, so closing readPfd does not unblock the pending read on the other handle; and closing it while another thread may still be about to call read() is a use-after-close on a possibly-recycled fd number.
- Kernel-side (
drivers/hid/uhid.c, uhid_char_read), a blocking read sleeps on the output-queue wait queue and only returns when an event is queued (or on a signal, which Thread.interrupt() does not deliver to a blocking JVM file read):
ret = wait_event_interruptible(uhid->waitq,
uhid->head != uhid->tail);
if (ret)
return ret;
...
len = min(count, sizeof(**uhid->outq));
if (copy_to_user(buffer, uhid->outq[uhid->tail], len)) {
ret = -EFAULT;
} else {
kfree(uhid->outq[uhid->tail]);
Note the kfree of the whole queued event regardless of how many bytes were copied — one read() call consumes exactly one event, which matters in the related stream-model issue.
Consequence: reader.join(READER_JOIN_MS) always times out, and one uhid-reader thread stays parked per createDevice() until that device is destroyed (keyboard attach/detach cycles accumulate parked threads within a session).
Also dead code in the same area: stopReader() nulls readerThread before the subsequent readerThread?.interrupt() call sites (e.g. in close()), so those interrupts are no-ops.
Suggested fix: replace the indefinite blocking read with a bounded wait that re-checks readerStop (e.g. Os.poll with a timeout on the fd, or available() polling through the existing UhidReadinessConfig.sleeper seam), so join() succeeds; keep the pfd open until the reader has exited.
Follow-up from the 1.4.2 review (#44, fix commit
ec43809, verified at53308c9).UhidChannel.stopReader()(app/src/main/java/com/inputleaf/android/shizuku/uhid/UhidChannel.kt:149-158) cannot actually wake a reader blocked inread()on/dev/uhid:Thread.interrupt()does not interrupt a blockingFileInputStream.read(onlyInterruptibleChannelI/O responds to interrupts).ParcelFileDescriptoris adup()of the same fd, which shares the kernel open file description, so closingreadPfddoes not unblock the pending read on the other handle; and closing it while another thread may still be about to callread()is a use-after-close on a possibly-recycled fd number.drivers/hid/uhid.c,uhid_char_read), a blocking read sleeps on the output-queue wait queue and only returns when an event is queued (or on a signal, whichThread.interrupt()does not deliver to a blocking JVM file read):Note the
kfreeof the whole queued event regardless of how many bytes were copied — oneread()call consumes exactly one event, which matters in the related stream-model issue.Consequence:
reader.join(READER_JOIN_MS)always times out, and oneuhid-readerthread stays parked percreateDevice()until that device is destroyed (keyboard attach/detach cycles accumulate parked threads within a session).Also dead code in the same area:
stopReader()nullsreaderThreadbefore the subsequentreaderThread?.interrupt()call sites (e.g. inclose()), so those interrupts are no-ops.Suggested fix: replace the indefinite blocking read with a bounded wait that re-checks
readerStop(e.g.Os.pollwith a timeout on the fd, oravailable()polling through the existingUhidReadinessConfig.sleeperseam), sojoin()succeeds; keep the pfd open until the reader has exited.