Repository: cpp-linter/cpp-linter (language: python)

Issue:
Fix tidy review write conflicts
This fixes write failures when writing back the unmodified contents of files modified by clang-tidy.

Closes #129 

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Enhanced functionality for running clang-tidy with improved error handling during file operations.
	- Implemented a timeout mechanism for file accessibility checks.

- **Bug Fixes**
	- Improved handling of file buffers to ensure the most recent changes are captured before processing.

- **Refactor**
	- Organized the flow of execution in the `_run_on_single_file` function for better readability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fix tidy review write conflicts
This fixes write failures when writing back the unmodified contents of files modified by clang-tidy.

Closes #129 

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Enhanced functionality for running clang-tidy with improved error handling during file operations.
	- Implemented a timeout mechanism for file accessibility checks.

- **Bug Fixes**
	- Improved handling of file buffers to ensure the most recent changes are captured before processing.

- **Refactor**
	- Organized the flow of execution in the `_run_on_single_file` function for better readability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Clang tidy review crashes when writing back file
I recently started getting a internal crash when running cpp-linter with tidy+format reviews enabled. For some reason there is a particular filename that it doesnt like.

Here is the excerpt from the github action log:
```
Get list of specified source files
  INFO:CPP Linter:Fetching files list from url: https://api.github.com/repos/Radeon-Pro/Capsaicin/pulls/892
  INFO:CPP Linter:Giving attention to the following files:
  	src/core/src/capsaicin/capsaicin_internal.cpp
  	src/core/src/capsaicin/capsaicin_internal.h
  	src/core/src/render_techniques/combine/combine.cpp
  	src/core/src/render_techniques/combine/combine.h
  	src/core/src/render_techniques/gi_denoiser/gi_denoiser.cpp
  	src/core/src/render_techniques/gi_denoiser/gi_denoiser.h
  	src/core/src/render_techniques/restir_gi/restir_gi.cpp
  	src/core/src/renderers/gi20/gi20_renderer.cpp
INFO:CPP Linter:`D:\a\_actions\cpp-linter\cpp-linter-action\v2\venv\Scripts\clang-format.EXE --version`: 18.1.8
INFO:CPP Linter:clang-format Ignoring the following paths/files/patterns:
	./.github
INFO:CPP Linter:`D:\a\_actions\cpp-linter\cpp-linter-action\v2\venv\Scripts\clang-tidy.EXE --version`: 18.1.8
INFO:CPP Linter:clang-tidy Ignoring the following paths/files/patterns:
	./.github
concurrent.futures.process._RemoteTraceback: 
"""
Traceback (most recent call last):
  File "C:\hostedtoolcache\windows\Python\3.10.11\x64\lib\concurrent\futures\process.py", line 246, in _process_worker
    r = call_item.fn(*call_item.args, **call_item.kwargs)
  File "D:\a\_actions\cpp-linter\cpp-linter-action\v2\venv\lib\site-packages\cpp_linter\clang_tools\__init__.py", line 53, in _run_on_single_file
    tidy_note = run_clang_tidy(
  File "D:\a\_actions\cpp-linter\cpp-linter-action\v2\venv\lib\site-packages\cpp_linter\clang_tools\clang_tidy.py", line 266, in run_clang_tidy
    Path(file_obj.name).write_bytes(original_buf)
  File "C:\hostedtoolcache\windows\Python\3.10.11\x64\lib\pathlib.py", line 1143, in write_bytes
    with self.open(mode='wb') as f:
  File "C:\hostedtoolcache\windows\Python\3.10.11\x64\lib\pathlib.py", line 1119, in open
    return self._accessor.open(self, mode, buffering, encoding, errors,
OSError: [Errno 22] Invalid argument: 'src\\core\\src\\capsaicin\\capsaicin_internal.h'
"""
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "C:\hostedtoolcache\windows\Python\3.10.11\x64\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\hostedtoolcache\windows\Python\3.10.11\x64\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "D:\a\_actions\cpp-linter\cpp-linter-action\v2\venv\Scripts\cpp-linter.exe\__main__.py", line 7, in <module>
  File "D:\a\_actions\cpp-linter\cpp-linter-action\v2\venv\lib\site-packages\cpp_linter\__init__.py", line 78, in main
    clang_versions = capture_clang_tools_output(files=files, args=args)
  File "D:\a\_actions\cpp-linter\cpp-linter-action\v2\venv\lib\site-packages\cpp_linter\clang_tools\__init__.py", line 163, in capture_clang_tools_output
    file_name, logs, tidy_advice, format_advice = future.result()
  File "C:\hostedtoolcache\windows\Python\3.10.11\x64\lib\concurrent\futures\_base.py", line 451, in result
    return self.__get_result()
  File "C:\hostedtoolcache\windows\Python\3.10.11\x64\lib\concurrent\futures\_base.py", line 403, in __get_result
    raise self._exception
OSError: [Errno 22] Invalid argument: 'src\\core\\src\\capsaicin\\capsaicin_internal.h'
Error: Process completed with exit code 1.
```

It doesnt like `src\\core\\src\\capsaicin\\capsaicin_internal.h` whereas `src\\core\\src\\capsaicin\\capsaicin_internal.cpp` is perfectly fine (along with every other file in the repo). Its not a privileges issue and there arent any special characters that could be an issue so im not sure why that exact filename causes problems.

Ive so far worked around it by adding a try/except around lines 263->268 in clang_tidy.py and ignoring the error. I then changed the order that format and tidy are run in `__init__.py` so that format is run first (if format has already run then does it even need to write back the original file contents?)


Discussion / hints:
## [Codecov](https://app.codecov.io/gh/cpp-linter/cpp-linter/pull/130?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`baee3c2`)](https://app.codecov.io/gh/cpp-linter/cpp-linter/commit/baee3c2970a57a07ec22413aecbc0e2de8eccc7a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter) to head [(`b7e42a0`)](https://app.codecov.io/gh/cpp-linter/cpp-linter/commit/b7e42a0799abbb94b5fa58325e41a6c041ca3b2b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #130   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           24        24           
  Lines         1837      1854   +17     
=========================================
+ Hits          1837      1854   +17     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cpp-linter/cpp-linter/pull/130?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter).

Been looking over this again. I think the logic might be lost if an error does occur. Here's my latest take:
```diff
--- a/cpp_linter/clang_tools/clang_tidy.py
+++ b/cpp_linter/clang_tools/clang_tidy.py
@@ -264,30 +264,41 @@ def run_clang_tidy(
         timeout = time.monotonic_ns() + 1000000
         # wait 1s for file to become accessible
         success = False
-        while time.monotonic_ns() < timeout:
+        exception = None
+        while not success and time.monotonic_ns() < timeout:
             try:
-                src = open(filename, "r+b")
-                # wait 1s for file to become readable
-                read_timeout = time.monotonic_ns() + 1000000
-                while not src.readable() and time.monotonic_ns() < read_timeout:
-                    pass
-                advice.patched = b"".join(src.readlines())
-                src.seek(os.SEEK_SET)  # back to start of file
-                # wait 1s for file to become writable
-                write_timeout = time.monotonic_ns() + 1000000
-                while not src.writable() and time.monotonic_ns() < write_timeout:
-                    pass
-                src.writelines([original_buf])
-                src.close()
-                success = True
+                with open(filename, "r+b") as src:
+                    # wait 1s for file to become readable
+                    read_timeout = time.monotonic_ns() + 1000000
+                    while not src.readable() and time.monotonic_ns() < read_timeout:
+                        pass  # pragma: no cover
+                    if src.readable():
+                        advice.patched = b"".join(src.readlines())
+                        src.seek(os.SEEK_SET)  # back to start of file
+                    else:  # pragma: no cover
+                        continue  # we need those changes before overwriting them
+
+                    # wait 1s for file to become writable
+                    write_timeout = time.monotonic_ns() + 1000000
+                    while not src.writable() and time.monotonic_ns() < write_timeout:
+                        pass  # pragma: no cover
+                    if src.writable():
+                        src.writelines([original_buf])
+                        success = True
                 break
-            except OSError:
-                if time.monotonic_ns() < timeout:
-                    pass
-                else:
-                    break
+
+            except OSError as exc:  # pragma: no cover
+                exception = exc
         if not success:  # pragma: no cover
-            logger.error("Failed to write back contents of file: %s", filename)
+            logger.error(
+                "Failed to write back contents of file: %s.%s)",
+                filename,
+                " timeout."
+                if not exception
+                else " {}: {}".format(
+                    exception.__class__.__name__, ", ".join(exception.args)
+                ),
+            )

``` 
> ```diff
> logger.error(
> +                "Failed to write back contents of file: %s.%s)",
> +                filename,
> +                " timeout."
> +                if not exception
> +                else " {}: {}".format(
> +                    exception.__class__.__name__, ", ".join(exception.args)
> +                ),
> +            )
> ```

Tested an seems to work correctly, updated the PR to match
<!-- This is an auto-generated comment: summarize by coderabbit.ai -->




<!-- walkthrough_start -->

## Walkthrough
The changes in this pull request involve modifications to the `_run_on_single_file` function in the `cpp_linter/clang_tools/__init__.py` file and enhancements to the `run_clang_tidy` function in the `cpp_linter/clang_tools/clang_tidy.py` file. The `_run_on_single_file` function's control flow is reorganized for clarity, while the `run_clang_tidy` function now includes improved error handling for file access, implementing a timeout mechanism to ensure files are accessible before reading or writing.

## Changes

| File Path                               | Change Summary                                                                                     |
|-----------------------------------------|---------------------------------------------------------------------------------------------------|
| `cpp_linter/clang_tools/__init__.py`   | Repositioned `format_advice` initialization before `tidy_cmd` check; removed previous logic.     |
| `cpp_linter/clang_tools/clang_tidy.py` | Added timeout mechanism for file access in `run_clang_tidy`; updated buffer reading logic.       |

## Assessment against linked issues

| Objective                                                | Addressed | Explanation                                      |
|---------------------------------------------------------|-----------|--------------------------------------------------|
| Clang tidy review crashes when writing back file (#129) | ✅        |                                                  |
| Enhanced error handling for file access issues (#129)   | ✅        |                                                  |

## Suggested labels
`enhancement`

## Poem
> 🐰 In the land of code where bunnies play,  
> We fixed the linter, hip-hip-hooray!  
> With tidy and format now in their place,  
> No more crashes, just a happy face!  
> Hopping through files, with joy we glide,  
> Thanks to our changes, we take it in stride! 🌟

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR. ([Experiment](https://docs.coderabbit.ai/early-access#experiments))
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
## [Codecov](https://app.codecov.io/gh/cpp-linter/cpp-linter/pull/130?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`baee3c2`)](https://app.codecov.io/gh/cpp-linter/cpp-linter/commit/baee3c2970a57a07ec22413aecbc0e2de8eccc7a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter) to head [(`b7e42a0`)](https://app.codecov.io/gh/cpp-linter/cpp-linter/commit/b7e42a0799abbb94b5fa58325e41a6c041ca3b2b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #130   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           24        24           
  Lines         1837      1854   +17     
=========================================
+ Hits          1837      1854   +17     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cpp-linter/cpp-linter/pull/130?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cpp-linter).

Been looking over this again. I think the logic might be lost if an error does occur. Here's my latest take:
```diff
--- a/cpp_linter/clang_tools/clang_tidy.py
+++ b/cpp_linter/clang_tools/clang_tidy.py
@@ -264,30 +264,41 @@ def run_clang_tidy(
         timeout = time.monotonic_ns() + 1000000
         # wait 1s for file to become accessible
         success = False
-        while time.monotonic_ns() < timeout:
+        exception = None
+        while not success and time.monotonic_ns() < timeout:
             try:
-                src = open(filename, "r+b")
-                # wait 1s for file to become readable
-                read_timeout = time.monotonic_ns() + 1000000
-                while not src.readable() and time.monotonic_ns() < read_timeout:
-                    pass
-                advice.patched = b"".join(src.readlines())
-                src.seek(os.SEEK_SET)  # back to start of file
-                # wait 1s for file to become writable
-                write_timeout = time.monotonic_ns() + 1000000
-                while not src.writable() and time.monotonic_ns() < write_timeout:
-                    pass
-                src.writelines([original_buf])
-                src.close()
-                success = True
+                with open(filename, "r+b") as src:
+                    # wait 1s for file to become readable
+                    read_timeout = time.monotonic_ns() + 1000000
+                    while not src.readable() and time.monotonic_ns() < read_timeout:
+                        pass  # pragma: no cover
+                    if src.readable():
+                        advice.patched = b"".join(src.readlines())
+                        src.seek(os.SEEK_SET)  # back to start of file
+                    else:  # pragma: no cover
+                        continue  # we need those changes before overwriting them
+
+                    # wait 1s for file to become writable
+                    write_timeout = time.monotonic_ns() + 1000000
+                    while not src.writable() and time.monotonic_ns() < write_timeout:
+                        pass  # pragma: no cover
+                    if src.writable():
+                        src.writelines([original_buf])
+                        success = True
                 break
-            except OSError:
-                if time.monotonic_ns() < timeout:
-                    pass
-                else:
-                    break
+
+            except OSError as exc:  # pragma: no cover
+                exception = exc
         if not success:  # pragma: no cover
-            logger.error("Failed to write back contents of file: %s", filename)
+            logger.error(
+                "Failed to write back contents of file: %s.%s)",
+                filename,
+                " timeout."
+                if not exception
+                else " {}: {}".format(
+                    exception.__class__.__name__, ", ".join(exception.args)
+                ),
+            )

``` 
> ```diff
> logger.error(
> +                "Failed to write back contents of file: %s.%s)",
> +                filename,
> +                " timeout."
> +                if not exception
> +                else " {}: {}".format(
> +                    exception.__class__.__name__, ", ".join(exception.args)
> +                ),
> +            )
> ```

Tested an seems to work correctly, updated the PR to match
<!-- This is an auto-generated comment: summarize by coderabbit.ai -->




<!-- walkthrough_start -->

## Walkthrough
The changes in this pull request involve modifications to the `_run_on_single_file` function in the `cpp_linter/clang_tools/__init__.py` file and enhancements to the `run_clang_tidy` function in the `cpp_linter/clang_tools/clang_tidy.py` file. The `_run_on_single_file` function's control flow is reorganized for clarity, while the `run_clang_tidy` function now includes improved error handling for file access, implementing a timeout mechanism to ensure files are accessible before reading or writing.

## Changes

| File Path                               | Change Summary                                                                                     |
|-----------------------------------------|---------------------------------------------------------------------------------------------------|
| `cpp_linter/clang_tools/__init__.py`   | Repositioned `format_advice` initialization before `tidy_cmd` check; removed previous logic.     |
| `cpp_linter/clang_tools/clang_tidy.py` | Added timeout mechanism for file access in `run_clang_tidy`; updated buffer reading logic.       |

## Assessment against linked issues

| Objective                                                | Addressed | Explanation                                      |
|---------------------------------------------------------|-----------|--------------------------------------------------|
| Clang tidy review crashes when writing back file (#129) | ✅        |                                                  |
| Enhanced error handling for file access issues (#129)   | ✅        |                                                  |

## Suggested labels
`enhancement`

## Poem
> 🐰 In the land of code where bunnies play,  
> We fixed the linter, hip-hip-hooray!  
> With tidy and format now in their place,  
> No more crashes, just a happy face!  
> Hopping through files, with joy we glide,  
> Thanks to our changes, we take it in stride! 🌟

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR. ([Experiment](https://docs.coderabbit.ai/early-access#experiments))
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
1. Are you sure it shouldn't be `src/` instead of `src/core/src/`? I don't know the repo structure.
2. Are you passing clang-tidy a compilation database?
3. Is your CI otherwise altering src files _while_ cpp-linter is running?

https://github.com/cpp-linter/cpp-linter/blob/baee3c2970a57a07ec22413aecbc0e2de8eccc7a/cpp_linter/clang_tools/clang_tidy.py#L262-L266
Could probably be written to use the same `Path` obj (for better garbage collection):
```python
    if tidy_review: 
        # store the modified output from clang-tidy
        temp = Path(file_obj.name)
        advice.patched = temp.read_bytes() 
        # re-write original file contents 
        temp.write_bytes(original_buf) 
``` 
> 1. Are you sure it shouldn't be `src/` instead of `src/core/src/`? I don't know the repo structure.

Paths are 100% correct, multiple other files in the repo all work fine just for some reason that one causes a failure. It makes no sense to me as the read on the previous line works fine

>  2. Are you passing clang-tidy a compilation database?

Yes

>  3. Is your CI otherwise altering src files _while_ cpp-linter is running?

No, just running the cpp-linter action

> ```python
> temp = Path(file_obj.name)
> ```

Gave this a try and still get the same error (was worth a try though)
Does it work better with the posix style path? 
https://github.com/cpp-linter/cpp-linter/blob/baee3c2970a57a07ec22413aecbc0e2de8eccc7a/cpp_linter/clang_tools/clang_tidy.py#L225
```py
temp = Path(filename)
```
I just don't understand how it would work to read the binary contents and then fail to write those bytes to the same file. Very strange. You can also turn on debugging logs with `--verbosity=debug`.
> Does it work better with the posix style path?

Tried that aswell, this is the code im currently using that atleast traps the exception and allows cpp-linter to continue (doesnt seem to have any issue with continuing as I also changed format to run before lint so I dont believe the output file is ever used again)
```
    if tidy_review:
        try:
            # store the modified output from clang-tidy
            temp = Path(filename)
            advice.patched = temp.read_bytes()
            # re-write original file contents
            temp.write_bytes(original_buf)
        except OSError:
            logger.error("Failed to restore original file contents: %s", filename)
```

Setting verbosity to debug doesnt show any additional information beyond the log I intially posted

I'm more inclined to lock the file and do it all at once before closing the file.
```py
timeout = time.monotonic_ns() + 1000000
# wait 1s for file to become accessible
while time.monotonic_ns() < timeout:
    try:
        src = open(filename, "r+b")

        # wait 1s for file to become readable
        read_timeout = time.monotonic_ns() + 1000000
        while not src.readable() and time.monotonic_ns() < read_timeout:
            pass
        advice.patched = b"".join(src.readlines())

        src.seek(os.SEEK_SET)  # back to start of file

        # wait 1s for file to become writable
        write_timeout = time.monotonic_ns() + 1000000
        while not src.writeable() and time.monotonic_ns() < write_timeout:
            pass
        src.writelines([original_buf])
        src.close()
        break
    except OSError as exc:
        if time.monotonic_ns() < timeout:
            pass
        else:
            raise from exc
```
I think the real problem is that other instances of clang-tidy are concurrently opening the file (probably as an `#include`d source). This is a problem rather specific to Windows.
> I think the real problem is that other instances of clang-tidy are concurrently opening the file (probably as an `#include`d source). This is a problem rather specific to Windows.

I think that is correct, the other files being run include the file thats crashing. Maybe clang-tidy should copy the input file to a temp one before running so that the input source is never actually modified


I had considered the temp file idea... I don't think its too feasible because of cyclical or relative `#include` paths.
> I'm more inclined to lock the file and do it all at once before closing the file.
> 
> ```python
> timeout = time.monotonic_ns() + 1000000
> # wait 1s for file to become accessible
> while time.monotonic_ns() < timeout:
>     try:
>         src = open(filename, "r+b")
> 
>         # wait 1s for file to become readable
>         read_timeout = time.monotonic_ns() + 1000000
>         while not src.readable() and time.monotonic_ns() < read_timeout:
>             pass
>         advice.patched = b"".join(src.readlines())
> 
>         src.seek(os.SEEK_SET)  # back to start of file
> 
>         # wait 1s for file to become writable
>         write_timeout = time.monotonic_ns() + 1000000
>         while not src.writeable() and time.monotonic_ns() < write_timeout:
>             pass
>         src.writelines([original_buf])
>         src.close()
>         break
>     except OSError as exc:
>         if time.monotonic_ns() < timeout:
>             pass
>         else:
>             raise from exc
> ```

Tested and this seems to fix the issue. I created a PR with the tested changes (with a few typo fixes from the original) #130 


Fix patch:
diff --git a/cpp_linter/clang_tools/__init__.py b/cpp_linter/clang_tools/__init__.py
index b762cdf..3deb6b9 100644
--- a/cpp_linter/clang_tools/__init__.py
+++ b/cpp_linter/clang_tools/__init__.py
@@ -6,7 +6,7 @@
 from typing import Optional, List, Dict, Tuple, cast
 import shutil
 
-from ..common_fs import FileObj
+from ..common_fs import FileObj, FileIOTimeout
 from ..common_fs.file_filter import TidyFileFilter, FormatFileFilter
 from ..loggers import start_log_group, end_log_group, worker_log_init, logger
 from .clang_tidy import run_clang_tidy, TidyAdvice
@@ -45,34 +45,52 @@ def _run_on_single_file(
     args: Args,
 ) -> Tuple[str, str, Optional[TidyAdvice], Optional[FormatAdvice]]:
     log_stream = worker_log_init(log_lvl)
-
-    tidy_note = None
-    if tidy_cmd is not None and (
-        tidy_filter is None or tidy_filter.is_source_or_ignored(file.name)
-    ):
-        tidy_note = run_clang_tidy(
-            command=tidy_cmd,
-            file_obj=file,
-            checks=args.tidy_checks,
-            lines_changed_only=args.lines_changed_only,
-            database=args.database,
-            extra_args=args.extra_arg,
-            db_json=db_json,
-            tidy_review=args.tidy_review,
-            style=args.style,
-        )
+    filename = Path(file.name).as_posix()
 
     format_advice = None
     if format_cmd is not None and (
         format_filter is None or format_filter.is_source_or_ignored(file.name)
     ):
-        format_advice = run_clang_format(
-            command=format_cmd,
-            file_obj=file,
-            style=args.style,
-            lines_changed_only=args.lines_changed_only,
-            format_review=args.format_review,
-        )
+        try:
+            format_advice = run_clang_format(
+                command=format_cmd,
+                file_obj=file,
+                style=args.style,
+                lines_changed_only=args.lines_changed_only,
+                format_review=args.format_review,
+            )
+        except FileIOTimeout:  # pragma: no cover
+            logger.error(
+                "Failed to read or write contents of %s when running clang-format",
+                filename,
+            )
+        except OSError:  # pragma: no cover
+            logger.error(
+                "Failed to open the file %s when running clang-format", filename
+            )
+
+    tidy_note = None
+    if tidy_cmd is not None and (
+        tidy_filter is None or tidy_filter.is_source_or_ignored(file.name)
+    ):
+        try:
+            tidy_note = run_clang_tidy(
+                command=tidy_cmd,
+                file_obj=file,
+                checks=args.tidy_checks,
+                lines_changed_only=args.lines_changed_only,
+                database=args.database,
+                extra_args=args.extra_arg,
+                db_json=db_json,
+                tidy_review=args.tidy_review,
+                style=args.style,
+            )
+        except FileIOTimeout:  # pragma: no cover
+            logger.error(
+                "Failed to Read/Write contents of %s when running clang-tidy", filename
+            )
+        except OSError:  # pragma: no cover
+            logger.error("Failed to open the file %s when running clang-tidy", filename)
 
     return file.name, log_stream.getvalue(), tidy_note, format_advice
 
diff --git a/cpp_linter/clang_tools/clang_format.py b/cpp_linter/clang_tools/clang_format.py
index a32139c..973d36b 100644
--- a/cpp_linter/clang_tools/clang_format.py
+++ b/cpp_linter/clang_tools/clang_format.py
@@ -130,12 +130,13 @@ def parse_format_replacements_xml(
         file_obj.range_of_changed_lines(lines_changed_only, get_ranges=True),
     )
     tree = ET.fromstring(xml_out)
+    content = file_obj.read_with_timeout()
     for child in tree:
         if child.tag == "replacement":
             null_len = int(child.attrib["length"])
             text = "" if child.text is None else child.text
             offset = int(child.attrib["offset"])
-            line, cols = get_line_cnt_from_cols(file_obj.name, offset)
+            line, cols = get_line_cnt_from_cols(content, offset)
             is_line_in_ranges = False
             for r in ranges:
                 if line in range(r[0], r[1]):  # range is inclusive
diff --git a/cpp_linter/clang_tools/clang_tidy.py b/cpp_linter/clang_tools/clang_tidy.py
index d5c0f5b..5b815a3 100644
--- a/cpp_linter/clang_tools/clang_tidy.py
+++ b/cpp_linter/clang_tools/clang_tidy.py
@@ -246,7 +246,7 @@ def run_clang_tidy(
     if tidy_review:
         # clang-tidy overwrites the file contents when applying fixes.
         # create a cache of original contents
-        original_buf = Path(file_obj.name).read_bytes()
+        original_buf = file_obj.read_with_timeout()
         cmds.append("--fix-errors")  # include compiler-suggested fixes
     cmds.append(filename)
     logger.info('Running "%s"', " ".join(cmds))
@@ -260,10 +260,8 @@ def run_clang_tidy(
     advice = parse_tidy_output(results.stdout.decode(), database=db_json)
 
     if tidy_review:
-        # store the modified output from clang-tidy
-        advice.patched = Path(file_obj.name).read_bytes()
-        # re-write original file contents
-        Path(file_obj.name).write_bytes(original_buf)
+        # store the modified output from clang-tidy and re-write original file contents
+        advice.patched = file_obj.read_write_with_timeout(original_buf)
 
     return advice
 
diff --git a/cpp_linter/clang_tools/patcher.py b/cpp_linter/clang_tools/patcher.py
index 50313d2..82a56c7 100644
--- a/cpp_linter/clang_tools/patcher.py
+++ b/cpp_linter/clang_tools/patcher.py
@@ -2,7 +2,6 @@
 by the clang tool's output."""
 
 from abc import ABC
-from pathlib import Path
 from typing import Optional, Dict, Any, List, Tuple
 from pygit2 import Patch  # type: ignore
 from ..common_fs import FileObj
@@ -178,7 +177,7 @@ def get_suggestions_from_patch(
             self.patched
         ), f"{self.__class__.__name__} has no suggestions for {file_obj.name}"
         patch = Patch.create_from(
-            Path(file_obj.name).read_bytes(),
+            file_obj.read_with_timeout(),
             self.patched,
             file_obj.name,
             file_obj.name,
diff --git a/cpp_linter/common_fs/__init__.py b/cpp_linter/common_fs/__init__.py
index 974b8dd..38032db 100644
--- a/cpp_linter/common_fs/__init__.py
+++ b/cpp_linter/common_fs/__init__.py
@@ -1,5 +1,6 @@
 from os import environ
 from pathlib import Path
+import time
 from typing import List, Dict, Any, Union, Tuple, Optional, TYPE_CHECKING
 from pygit2 import DiffHunk  # type: ignore
 from ..loggers import logger
@@ -158,6 +159,90 @@ def is_range_contained(self, start: int, end: int) -> Optional[Tuple[int, int]]:
         )
         return None
 
+    def read_with_timeout(self, timeout_ns: int = 1_000_000_000) -> bytes:
+        """Read the entire file's contents.
+
+        :param timeout_ns: The number of nanoseconds to wait till timeout occurs.
+            Defaults to 1 second.
+
+        :returns: The bytes read from the file.
+
+        :raises FileIOTimeout: When the operation did not succeed due to a timeout.
+        :raises OSError: When the file could not be opened due to an `OSError`.
+        """
+        contents = b""
+        success = False
+        exception: Union[OSError, FileIOTimeout] = FileIOTimeout(
+            f"Failed to read from file '{self.name}' within "
+            + f"{round(timeout_ns / 1_000_000_000, 2)} seconds"
+        )
+        timeout = time.monotonic_ns() + timeout_ns
+        while not success and time.monotonic_ns() < timeout:
+            try:
+                with open(self.name, "rb") as f:
+                    while not success and time.monotonic_ns() < timeout:
+                        if f.readable():
+                            contents = f.read()
+                            success = True
+                        else:  # pragma: no cover
+                            time.sleep(0.001)  # Sleep to prevent busy-waiting
+            except OSError as exc:  # pragma: no cover
+                exception = exc
+        if not success and exception:  # pragma: no cover
+            raise exception
+        return contents
+
+    def read_write_with_timeout(
+        self,
+        data: Union[bytes, bytearray],
+        timeout_ns: int = 1_000_000_000,
+    ) -> bytes:
+        """Read then write the entire file's contents.
+
+        :param data: The bytes to write to the file. This will overwrite the contents
+            being read beforehand.
+        :param timeout_ns: The number of nanoseconds to wait till timeout occurs.
+            Defaults to 1 second.
+
+        :returns: The bytes read from the file.
+
+        :raises FileIOTimeout: When the operation did not succeed due to a timeout.
+        :raises OSError: When the file could not be opened due to an `OSError`.
+        """
+        success = False
+        exception: Union[OSError, FileIOTimeout] = FileIOTimeout(
+            f"Failed to read then write file '{self.name}' within "
+            + f"{round(timeout_ns / 1_000_000_000, 2)} seconds"
+        )
+        original_data = b""
+        timeout = time.monotonic_ns() + timeout_ns
+        while not success and time.monotonic_ns() < timeout:
+            try:
+                with open(self.name, "r+b") as f:
+                    while not success and time.monotonic_ns() < timeout:
+                        if f.readable():
+                            original_data = f.read()
+                            f.seek(0)
+                        else:  # pragma: no cover
+                            time.sleep(0.001)  # Sleep to prevent busy-waiting
+                            continue
+                        while not success and time.monotonic_ns() < timeout:
+                            if f.writable():
+                                f.write(data)
+                                f.truncate()
+                                success = True
+                            else:  # pragma: no cover
+                                time.sleep(0.001)  # Sleep to prevent busy-waiting
+            except OSError as exc:  # pragma: no cover
+                exception = exc
+        if not success and exception:  # pragma: no cover
+            raise exception
+        return original_data
+
+
+class FileIOTimeout(Exception):
+    """An exception thrown when a file operation timed out."""
+
 
 def has_line_changes(
     lines_changed_only: int, diff_chunks: List[List[int]], additions: List[int]
@@ -180,10 +265,10 @@ def has_line_changes(
     )
 
 
-def get_line_cnt_from_cols(file_path: str, offset: int) -> Tuple[int, int]:
+def get_line_cnt_from_cols(data: bytes, offset: int) -> Tuple[int, int]:
     """Gets a line count and columns offset from a file's absolute offset.
 
-    :param file_path: Path to file.
+    :param data: Bytes content to analyze.
     :param offset: The byte offset to translate
 
     :returns:
@@ -193,5 +278,5 @@ def get_line_cnt_from_cols(file_path: str, offset: int) -> Tuple[int, int]:
         - Index 1 is the column number for the given offset on the line.
     """
     # logger.debug("Getting line count from %s at offset %d", file_path, offset)
-    contents = Path(file_path).read_bytes()[:offset]
+    contents = data[:offset]
     return (contents.count(b"\n") + 1, offset - contents.rfind(b"\n"))


Test patch:
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 0f02575..2af3d4f 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -84,8 +84,8 @@ def test_list_src_files(
 @pytest.mark.parametrize("line,cols,offset", [(13, 5, 144), (19, 1, 189)])
 def test_file_offset_translation(line: int, cols: int, offset: int):
     """Validate output from ``get_line_cnt_from_cols()``"""
-    test_file = str(Path("tests/demo/demo.cpp").resolve())
-    assert (line, cols) == get_line_cnt_from_cols(test_file, offset)
+    contents = Path("tests/demo/demo.cpp").read_bytes()
+    assert (line, cols) == get_line_cnt_from_cols(contents, offset)
 
 
 def test_serialize_file_obj():