Skip to content

UHID readiness reader cannot be stopped (blocked read outlives stopReader) #48

Description

@guaje

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions