Skip to content

darwin: keep the parent device link correct across re-enumeration - #1917

Open
sonatique wants to merge 2 commits into
libusb:masterfrom
sonatique:darwin-parent-device-reenumeration
Open

darwin: keep the parent device link correct across re-enumeration#1917
sonatique wants to merge 2 commits into
libusb:masterfrom
sonatique:darwin-parent-device-reenumeration

Conversation

@sonatique

@sonatique sonatique commented Aug 3, 2026

Copy link
Copy Markdown
Member

Two fixes in the darwin re-enumeration path, found while investigating the
libusb_get_port_numbers / process_new_device report on #1798. They are different
kinds of fix in the same code, so they are separate commits. A companion fix bounding
the reader's fill loop is in PR#1915.

darwin: refresh the cached parent session on re-enumeration (functional, no
concurrency involved)

darwin_get_cached_device assigns parent_session on every path, not only when it
allocates a cached device, so a device returning from a re-enumeration carries the
session id of the parent it currently sits behind. When a parent hub is re-enumerated
and receives a new session id, its children resolve that new id the next time they are
re-enumerated, and libusb_get_parent and libusb_get_port_numbers keep reporting the
real topology.

darwin: publish the new parent device before releasing the old one (addendum item
12 of #1798)

process_new_device resolves the new parent and stores it before releasing the previous
one, so dev->parent_dev always holds either NULL or a device this device owns a
reference to. A concurrent libusb_get_port_numbers walk sees one parent or the other,
with no NULL gap while the lookup runs. Reference counting stays balanced:
usbi_get_device_by_session_id returns a referenced device, so this is the same +1/-1
pair in the opposite order.

It narrows the reader race rather than closing it. A walk that already loaded the old
pointer can still dereference it after the release, and closing that needs the reader to
hold a reference, which is a core-level change.

Reference: #1798 (addendum item 12)

Regarding testing:
The first commit has a visible symptom and is the one worth exercising: with a device
behind a hub, replug or re-enumerate the hub, then reset the child, and check that
libusb_get_parent and the port path still resolve. hotplugtest plus testlibusb
covers it.

The second has no targeted reproducer, so the usual suite for no-regression
is enough.

If somebody re-runs the item-9/10 tester (its scan thread calls libusb_get_port_numbers),
the TSan report on parent_dev still appears. It flags the unlocked read of the field,
which neither commit changes; the recent clean runs used tests that never walk the
parent chain, so they did not exercise it. Silencing it needs the field made atomic, or
both the reader and the backend writer holding a common lock, with ctx->usb_devs_lock
the natural candidate since usbi_hotplug_exit already walks parent chains under it.
Both are core changes, and separate from the use-after-free question above.

darwin_get_cached_device assigns parent_session on every path, not
only when it allocates a cached device, so a device returning from a
re-enumeration carries the session id of the parent it currently sits
behind.

A parent hub that is itself re-enumerated receives a new session id.
Its children now resolve that new id the next time they are
re-enumerated, so libusb_get_parent keeps returning the parent and
libusb_get_port_numbers keeps reporting the full path.

Reference: libusb#1798
process_new_device resolves the new parent and stores it into
dev->parent_dev before releasing the previous one, so the field always
holds either NULL or a device this device owns a reference to.

The device being updated is already visible to the application, and
libusb_get_port_numbers walks the parent chain without a lock. A
concurrent walk therefore sees one parent or the other for the whole
update, with no NULL gap while the lookup runs and no pointer to a
device that has been freed. That matters because the reader measures
the chain depth and fills the caller's array in two separate passes,
and a chain that changes length between them yields a result that does
not match what was measured. Where the parent is unchanged the stored
value is identical, so no transition is observable at all.

Reference counting stays balanced: usbi_get_device_by_session_id
returns a referenced device, so this is the same +1/-1 pair in the
opposite order.

The window is narrowed rather than closed. A walk that loaded the old
pointer before the store can still dereference it after the release;
closing that requires the reader to hold a reference, which is a
core-level change.

Reference: libusb#1798 (addendum item 12)
@Youw

Youw commented Aug 4, 2026

Copy link
Copy Markdown
Member

@sonatique I assume this was (at least partially) generated by AI? or was this all you?

@sonatique

Copy link
Copy Markdown
Member Author

@sonatique I assume this was (at least partially) generated by AI? or was this all you?

@Youw: the code (2 commits) is mine, but indeed the PR description is from Claude,

I was working concurrently on this PR and the more complicated #1919. I had these commits done on one branch and started the other one on top, but I finally decided to rely on Claude for #1919 because I was getting lost.

When done, I re-separated the commits to make 2 PRs and asked Claude to make the PR description for that one too, out of laziness.

Should I add a line saying the description is AI generated?

@Youw

Youw commented Aug 4, 2026

Copy link
Copy Markdown
Member

Should I add a line saying the description is AI generated?

That is still an open question.

For the most part people mostly care about the code being "not poluted by AI bugs".

When I see this much of the text in PR description, I automatically assume human is too lazy to write it, so it must be an AI. But then, if AI has written the description, maybe the code too. But it is not the case this time...

Maybe we should extend our AGENTS.md to have Assisted-by: attribution if the code was AI generated, and have the Description assisted-by: in case if AI generated the description/title, but the code was written by human.

@Youw

Youw commented Aug 4, 2026

Copy link
Copy Markdown
Member

Codex reviewed both commits at 67dc4536e2736bdfa1df093080b89039a3caff53 against their base and current master (including the merged #1915). No actionable defects were found in this diff.

  • parent_session is now refreshed on the location/in_reenumerate reuse path; fresh-device behavior is unchanged.
  • The parent swap keeps reference accounting balanced when the old and new parents are identical, different, or the new lookup returns NULL: the lookup's new reference is acquired before the old reference is released.
  • This remains a partial mitigation of Darwin backend: concurrency audit (8 issues, partial PR coverage, proposed roadmap) #1798 item 12, not a complete concurrency fix. parent_dev is still read and written without common synchronization, and a walker that loaded the old pointer can still dereference it after the writer releases it. The PR description already states this accurately, so the existing TSan report is expected to remain.
  • git diff --check is clean, and the macOS workflow for this head completed successfully.

Generated by Codex (GPT-5.6 Sol).

@seanm

seanm commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

...and asked Claude to make the PR description for that one too, out of laziness.

The problem is: these AIs are always so verbose, reading everything they output takes so much time. "If you're too lazy to write it, then I'm too lazy to read it" is a sentiment I'm seeing more and more, and I'm starting to appreciate it. It's easy to generate tonnes and tonnes of AI text, but hard and slow to read it all. (I'm not trying to pick on you here, this is happening everywhere.)

@Youw

Youw commented Aug 5, 2026

Copy link
Copy Markdown
Member

these AIs are always so verbose

There is two sides to the coin to this.

I agree by default it generates tons of code and reading it by a human usually exhausting. This actually can easily be adjusted by giving a corresponding AGENTS.md directive.

On the other hand, I usually use other agents to read/analyze/fix and give me a short summary, and in such cases - the more context provided for input - the higher chance to get an accurate secondary analysys.

@mcuee

mcuee commented Aug 7, 2026

Copy link
Copy Markdown
Member

Let me test this PR over the weekend.

@mcuee
mcuee self-requested a review August 7, 2026 02:19
@mcuee

mcuee commented Aug 7, 2026

Copy link
Copy Markdown
Member

First test -- make check passes the normal build, TSAN debug build and ASAN debug build.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants