FFFF
Skip to content

hdhomerun stale lock fix and DVR recording deadlock fix#1993

Open
jefflessard wants to merge 5 commits intotvheadend:masterfrom
jefflessard:master
Open

hdhomerun stale lock fix and DVR recording deadlock fix#1993
jefflessard wants to merge 5 commits intotvheadend:masterfrom
jefflessard:master

Conversation

@jefflessard
Copy link
Copy Markdown

No description provided.

Signed-off-by: Jean-François Lessard <jefflessard3@gmail.com>
Signed-off-by: Jean-François Lessard <jefflessard3@gmail.com>
Signed-off-by: Jean-François Lessard <jefflessard3@gmail.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @jefflessard, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses two distinct but critical stability issues: a potential deadlock in the DVR recording module and problems with stale tuner locks on HDHomeRun devices. The DVR changes prevent a thread from attempting to join itself during shutdown, which could lead to a deadlock, and ensure proper resource cleanup in error scenarios. The HDHomeRun modifications introduce proactive and reactive measures to identify and clear orphaned tuner locks, which can occur after crashes or improper shutdowns, thereby improving the reliability and availability of tuners.

Highlights

  • DVR Deadlock Prevention: Implemented a check to prevent a DVR recording thread from deadlocking itself when attempting to join during shutdown.
  • HDHomeRun Stale Lock Resolution: Introduced a mechanism to detect and clear orphaned tuner locks on HDHomeRun devices, improving tuner availability.
  • Consistent Postprocessing: Ensured that DVR postprocessing and memory cleanup are consistently handled, even when recording fails early.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces two important fixes. First, it resolves a deadlock in the DVR recording logic by preventing a thread from joining itself. The related post-processing logic is also adjusted to handle this case. Second, it adds a mechanism to detect and clear stale locks on HDHomeRun tuners, which can be left behind after a crash, improving the reliability of tuning.

My review focuses on the correctness of these fixes. While the HDHomeRun stale lock handling appears robust, I've found a critical use-after-free vulnerability in the DVR deadlock fix. The mitigation for a memory leak inadvertently creates a dangling pointer that is returned by the DVR thread. I've provided a detailed comment and a code suggestion to address this.

Copy link
Copy Markdown
Contributor
Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request addresses two critical issues in tvheadend: stale HDHomeRun device locks that prevent tuning, and a potential deadlock when stopping DVR recordings from within the worker thread.

Key Changes:

  • Adds automatic detection and clearing of orphaned HDHomeRun tuner locks from previous sessions
  • Implements deadlock prevention in DVR recording cleanup by detecting self-join scenarios
  • Handles postprocessing memory management in error paths

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/input/mpegts/tvhdhomerun/tvhdhomerun_private.h Adds declaration for the new stale lock clearing function
src/input/mpegts/tvhdhomerun/tvhdhomerun_frontend.c Implements stale lock detection/clearing and integrates it into the tuning workflow
src/input/mpegts/tvhdhomerun/tvhdhomerun.c Clears orphaned locks on all tuners during device discovery
src/dvr/dvr_rec.c Implements deadlock prevention and manages postproc memory in error paths


for (j = 0; j < dInfo->tuner_count; j++) {
hd = hdhomerun_device_create(dInfo->device_id, dInfo->ip_addr, j, NULL);

Copy link
Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace should be removed for code consistency.

Suggested change

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduced readability IMO


dvr_stop_recording(de, code == SM_CODE_NO_SPACE ? SM_CODE_NO_SPACE : SM_CODE_INVALID_TARGET, 1, 0);
}

Copy link
Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace should be removed for code consistency.

Suggested change

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduced readability IMO

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not talking about the empty line but about the whitespaces in the empty line, everywhere else in this file empty lines do not have any whitespaces.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

@jefflessard
Copy link
Copy Markdown
Author

I am sorry if my responses may sound harsh but this copilot automated review is very deceiving, of the level of a rookie developer. It basically focus on minor details (maybe except the dangling pointer) rather than understanding the importance of the fix and why it has been done this way.

@Flole998
Copy link
Copy Markdown
Member

/gemini It looks like your detailed comment was not added, could you please add it again?

@gemini-code-assist
Copy link
Copy Markdown

Hello @Flole998,

Thank you for reaching out. My initial comment on this pull request was a high-level summary of the changes. Detailed review comments are typically posted directly on the lines of code that are being discussed.

I understand from @jefflessard's previous comment that the initial review might have focused too much on minor details. I appreciate this feedback and will strive to provide more contextual and impactful suggestions in future reviews.

If you would like me to perform a full, detailed code review of the current state of the pull request, please use the command /gemini review. This will trigger a comprehensive analysis, and I will ensure to prioritize the importance and intent of the fixes, as highlighted by @jefflessard.

Please let me know if you have any other questions or if there's anything else I can assist with.

@Flole998
Copy link
Copy Markdown
Member

/gemini review

Copy link
Copy Markdown
@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces two important fixes for HDHomeRun and DVR functionality. The HDHomeRun changes correctly handle stale tuner locks by clearing them on device discovery and before attempting to acquire a lock. This should improve reliability, especially after a Tvheadend crash. The DVR changes address a deadlock that could occur when a recording thread tries to join itself. The fix is sound, but I've identified a critical use-after-free vulnerability that is introduced as a side effect, which needs to be addressed.

if (prch->prch_muxer)
dvr_thread_epilog(de, postproc);

free(postproc);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This free(postproc) call correctly prevents a memory leak that would otherwise occur due to the deadlock prevention logic. However, it introduces a use-after-free vulnerability.

The dvr_thread function, which calls dvr_thread_rec_start, is unaware that postproc has been freed. When dvr_thread exits, it returns this dangling pointer. If another thread calls pthread_join on dvr_thread, it will receive a pointer to freed memory, which could lead to a crash or other undefined behavior.

A robust solution is to ensure the caller is aware of the memory being freed. One way to do this is to change the function signature to accept a pointer to the pointer:
dvr_thread_rec_start(..., char **postproc)

Then, you can nullify the pointer in the caller's scope after freeing it:

free(*postproc);
*postproc = NULL;

This would require updating the call site in dvr_thread to pass &postproc, and dereferencing postproc within this function. Although the call site is outside the current diff, this change is crucial for memory safety.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! That's what I was saying in previous comments: passing postproc pointer by reference and cha 17A6 nging the signature (removing const) is a non-issue.

@sonarqubecloud
Copy link
Copy Markdown

@jefflessard
Copy link
Copy Markdown
Author

@Flole998 latests 2 commits should now properly address the discussed comments.

@jefflessard
Copy link
Copy Markdown
Author

/gemini review

Copy link
Copy Markdown
@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces two important fixes: one for a deadlock in DVR recording and another for handling stale locks on HDHomeRun tuners. The deadlock fix in dvr_rec.c correctly prevents a thread from joining itself, and the related changes to handle resource cleanup in this scenario are well-implemented. The stale lock fix for HDHomeRun devices proactively clears orphaned locks at device discovery and before tuning, which should improve reliability.

I've found two issues of high severity:

  1. A potential resource leak in the DVR deadlock fix, where a muxer might not be cleaned up.
  2. A memory leak in the new HDHomeRun stale lock handling logic.

Details and suggestions are in the line comments. Apart from these issues, the changes are good.

Comment on lines +213 to +214
if (postproc && prch->prch_muxer)
dvr_thread_epilog(de, postproc);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The condition for calling dvr_thread_epilog has been changed from if (prch->prch_muxer) to if (postproc && prch->prch_muxer). This is incorrect and will lead to a resource leak. If no post-processing script is configured, postproc will be NULL, and dvr_thread_epilog will not be called. This means the muxer (prch->prch_muxer) will not be closed or destroyed. The dvr_thread_epilog function handles a NULL postproc argument safely, so the original condition was correct. Please revert this condition to if (prch->prch_muxer).

    if (prch->prch_muxer)
      dvr_thread_epilog(de, postproc);

Comment on lines +336 to +390
bool tvhdhomerun_clear_stale_lock(struct hdhomerun_device_t *hd)
{
char *lockkey_owner = NULL;
char local_ip_str[64];
uint32_t local_ip = 0;
int res;

assert(hd != NULL);

// Get the local IP address as seen by the device
local_ip = hdhomerun_device_get_local_machine_addr(hd);
if (local_ip == 0) {
tvhwarn(LS_TVHDHOMERUN, "failed to query local_ip");
return false;
}

// Convert IP to dotted-quad string format
snprintf(local_ip_str, sizeof(local_ip_str), "%u.%u.%u.%u",
(local_ip >> 24) & 0xFF,
(local_ip >> 16) & 0xFF,
(local_ip >> 8) & 0xFF,
local_ip & 0xFF);

tvhtrace(LS_TVHDHOMERUN, "local_ip=%s", local_ip_str);

// Query current lock holder
res = hdhomerun_device_get_tuner_lockkey_owner(hd, &lockkey_owner);
if (res < 0) {
tvhwarn(LS_TVHDHOMERUN, "failed to query lockkey (res=%d)", res);
return false;
}

if (lockkey_owner == NULL || strcmp(lockkey_owner, "none") == 0) {
tvhtrace(LS_TVHDHOMERUN, "no lockkey set");
return false;
}

// Check if the lock is held by ourselves
if (strcmp(lockkey_owner, local_ip_str) != 0) {
tvhinfo(LS_TVHDHOMERUN,
"tuner locked by different host: %s (not clearing)",
lockkey_owner);
return false;
}

// Lock is held by OUR IP - this is an orphaned lock from a previous session
tvhinfo(LS_TVHDHOMERUN,
"detected orphaned self-lock (%s), force-releasing",
lockkey_owner);

// Force-release the orphaned lock
hdhomerun_device_tuner_lockkey_force(hd);

return true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The lockkey_owner string, which is allocated by hdhomerun_device_get_tuner_lockkey_owner, is not freed in any of the return paths of this function. This will cause a memory leak. You should free(lockkey_owner) before each return statement after hdhomerun_device_get_tuner_lockkey_owner has been called. A goto cleanup pattern might be cleaner to ensure it's always freed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the check, but this is a false positive. hdhomerun_device_get_tuner_lockkey_owner returns the pointer produced by hdhomerun_control_get, and the control API explicitly documents that the returned string is owned by the control-socket and "will remain valid until the next call to a control sock function." Callers must not free that pointer.

  • hdhomerun_control.h documents the ownership/lifetime: the char* returned via the pvalue/powner parameter is an internal buffer owned by the control socket and "will remain valid until the next call to a control sock function." It is not caller-owned memory to free.
  • hdhomerun_device_get_tuner_lockkey_owner simply forwards to the control API:
    • Implementation: it builds "/tuner%u/lockkey" then returns hdhomerun_control_get(hd->cs, name, powner, NULL);
    • So the pointer you get from hdhomerun_device_get_tuner_lockkey_owner is exactly the pointer returned by hdhomerun_control_get and therefore has the same ownership rules.

Consequences:

  • Calling free(lockkey_owner) in tvhdhomerun_clear_stale_lock would be wrong (double-free / free of library-owned buffer) and can crash or corrupt memory.
  • The current code that does not free lockkey_owner is correct. If the code needs the string past the next control-socket call (or past the lifetime of the control socket), it should strdup() it and free its own copy later.

@Trujulu
Copy link
Copy Markdown
Contributor
Trujulu commented Dec 29, 2025

Hello, I’ve reviewed this PR regarding the lockkey issue #2022

Regarding the forced unlock solution, I believe this behavior should be configurable and disabled by default, ideally via the tuner settings.

There are scenarios where tvheadend runs on the same machine or IP as other software, and in such cases forcibly releasing the stream could cause conflicts or unintended disruptions.

For this reason, I think this should be an optional feature that users can explicitly enable when needed.

PS: I performed several tests and observed that the HDHomeRun firmware does not release the tuner after 30 seconds without data, including cases where tvheadend is restarted or the consuming machine is fully rebooted.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

0