{
 "number": 33646,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/33646",
 "title": "log: check fclose() results and report safely in logging.cpp",
 "author": "cedwies",
 "author_association": "CONTRIBUTOR",
 "created_at": "2025-10-17T19:06:08Z",
 "updated_at": "2026-09-17T09:01:37Z",
 "age_days": 334,
 "draft": false,
 "labels": [
  "Utils/log/libs"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "a41ae57707f9efd1114689a427cffc92b7722139",
 "head_ref": "log-safe-fclose",
 "head_repo": "cedwies/bitcoin",
 "head_history": [
  {
   "t": "2025-10-17T19:24:35Z",
   "sha": "f1720f8fe108ca8ce1647ed4452576dbe6d496c0"
  },
  {
   "t": "2025-10-17T20:28:03Z",
   "sha": "ddfef829f738e946ac02a84107679a674a58ca9e"
  },
  {
   "t": "2025-12-11T13:24:11Z",
   "sha": "72e0cbc2665cc7143b637cc82834d38e28aae4b8"
  },
  {
   "t": "2026-05-15T09:20:16Z",
   "sha": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a"
  },
  {
   "t": "2026-07-28T08:34:00Z",
   "sha": "a41ae57707f9efd1114689a427cffc92b7722139"
  }
 ],
 "additions": 29,
 "deletions": 8,
 "changed_files": 1,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": []
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "ajtowns",
   "maflcko",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-07-28T12:06:39Z",
  "last_reviewer_activity": "2026-09-08T16:18:32Z",
  "last_reviewer": "sedited",
  "author_silent_days": 51,
  "waiting_on_author_days": 8,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/logging.cpp"
 ],
 "body": "`fclose()` can report write errors (for example if the disk is full or the filesystem has a problem). Right now, some `fclose()` calls in `src/logging.cpp` ignore the return value. This means errors might go unnoticed and log lines could be lost without warning.\n\n## What this PR does:\n- Add a small helper that prints `fclose()` errors to `stderr` (with path and errno).\n- In shutdown: close `m_fileout` safely and report errors.\n- In reopen: open the new file, swap it in, close the old one, and report errors if closing fails.\n- In shrink/rotate: check all `fclose(file)` calls and report failures.\n\nNo other behavior changes. Normal logging, rotation, and console output remain unchanged.",
 "commits": [
  {
   "sha": "a41ae57707f9efd1114689a427cffc92b7722139",
   "date": "2026-07-28T08:31:42Z",
   "message": "log: check fclose() results and report safely\n\nSome buffered I/O errors surface only on fclose(). Several fclose() calls in\nsrc/logging.cpp ignored the return value which can risk silent loss of log lines\nor misleading success during rotate/truncate.\n\nCheck return of fclose() and include errno in diagnostics. For the active log sink,\ndetach under lock and close outside the lock. On failure, report directly\nto stderr (which avoids recursive logging) and flush. No behavior change on success."
  }
 ],
 "timeline": [
  {
   "t": "2025-10-17T19:24:35Z",
   "kind": "force_push",
   "who": "cedwies",
   "commit": "f1720f8fe108ca8ce1647ed4452576dbe6d496c0"
  },
  {
   "t": "2025-10-17T20:28:03Z",
   "kind": "force_push",
   "who": "cedwies",
   "commit": "ddfef829f738e946ac02a84107679a674a58ca9e"
  },
  {
   "t": "2025-12-11T02:46:41Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": null,
   "text": "What's the value in having this outside the `m_cs` guard?"
  },
  {
   "t": "2025-12-11T03:09:08Z",
   "kind": "review",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "29a52aa157e85e70f939ced6cdd1ff4b988cc567",
   "text": "Should rebase on top of master rather than including a merge commit."
  },
  {
   "t": "2025-12-11T13:24:11Z",
   "kind": "force_push",
   "who": "cedwies",
   "commit": "72e0cbc2665cc7143b637cc82834d38e28aae4b8"
  },
  {
   "t": "2025-12-11T13:30:15Z",
   "kind": "review_comment",
   "who": "cedwies",
   "assoc": "CONTRIBUTOR",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": 2608921951,
   "text": "I put the `fclose` outside the lock to avoid holding the mutex during potentially slow I/O (like flushing the file). We swap `m_fileout` inside, so the state is safe, and then close the old file without blocking other threads. `ReportLogIOError` is designed to be lock-free anyway, if I am not missing something."
  },
  {
   "t": "2026-03-05T08:46:41Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "This looks ok, but is there a way to consistently trigger an fclose error so this might be tested locally?"
  },
  {
   "t": "2026-03-24T11:01:03Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nObviously, a logic error (like erroneous double-fclose) can be added manually inside the code. An alternative to mock the fs to return an IO error likely won't work, because there is no buffer to be flushed (`setbuf(new_fileout, nullptr); // unbuffered`), so any IO error will either happen on the write, or never? So I guess this only leaves manual strace fault injection?"
  },
  {
   "t": "2026-04-19T07:57:26Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "@cedwies ping for rebase."
  },
  {
   "t": "2026-05-15T09:20:16Z",
   "kind": "force_push",
   "who": "cedwies",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a"
  },
  {
   "t": "2026-05-15T10:05:39Z",
   "kind": "comment",
   "who": "cedwies",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased on current master.\n\nI don\u2019t see an easy clean unit test for this without adding some kind of FILE*/fs fault injection. Since the log file is unbuffered, normal write errors should usually show up on fwrite, as @maflcko said. So I think this is mainly a small defensive check for close/writeback errors."
  },
  {
   "t": "2026-05-16T08:01:03Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": null,
   "text": "The comment should be on the function, not the namespace?"
  },
  {
   "t": "2026-05-23T13:31:47Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": 2608921951,
   "text": "Is the idea that `ReportLogIOError` is never called under a lock? Reading through the other calls to the function, I do see some scenarios where the lock is being held. So changing this for this single test-only path seems like a poor motivation."
  },
  {
   "t": "2026-05-23T13:34:39Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": null,
   "text": "Nit (clang-format): There are some whitespace issues throughout this change. Can you run clang-format over the patch? I usually use the following command (and then only selectively apply the patches where they make sense): `git diff -U0 HEAD~1.. | ./contrib/devtools/clang-format-diff.py -p1 -i -v`."
  },
  {
   "t": "2026-07-10T19:44:08Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "@cedwies there's been some pending review comments here for a while. Do you have time to get around to them?"
  },
  {
   "t": "2026-07-28T08:34:00Z",
   "kind": "force_push",
   "who": "cedwies",
   "commit": "a41ae57707f9efd1114689a427cffc92b7722139"
  },
  {
   "t": "2026-07-28T09:12:34Z",
   "kind": "review_comment",
   "who": "cedwies",
   "assoc": "CONTRIBUTOR",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": 3252493878,
   "text": "Done, the documentation is now attached directly to `CloseLogFile()`."
  },
  {
   "t": "2026-07-28T09:14:59Z",
   "kind": "review_comment",
   "who": "cedwies",
   "assoc": "CONTRIBUTOR",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": 3292854656,
   "text": "I ran clang-format using the suggested command."
  },
  {
   "t": "2026-07-28T09:15:43Z",
   "kind": "review_comment",
   "who": "cedwies",
   "assoc": "CONTRIBUTOR",
   "path": "src/logging.cpp",
   "commit": "24320452e43c4d5429ce8dc2bb5b027ebbb6058a",
   "in_reply_to": 2608921951,
   "text": "Agreed. I restored the close in `DisconnectTestLogger()` to run under `m_cs` for consistency with the other call sites."
  },
  {
   "t": "2026-07-28T12:06:39Z",
   "kind": "comment",
   "who": "cedwies",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased onto current master and addressed the pending review comments:\n1. moved the helper documentation onto the function;\n2. kept the `DisconnectTestLogger()` close under `m_cs`\n3. ran clang-format as suggested.\n\nI also consolidated the close/error reporting sequence into `CloseLogFile()`, so now all `fclose()` calls in `logging.cpp` are checked consistently  (including the `fseek()` failure path). The duplicated numeric errno was removed, too.\n@sedited @maflcko, re-review would be appreciated when convenient, thank you for your patience."
  },
  {
   "t": "2026-09-08T15:46:05Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a41ae57707f9efd1114689a427cffc92b7722139",
   "in_reply_to": null,
   "text": "Nit: Can just take errno here directly as we do everywhere else."
  },
  {
   "t": "2026-09-08T15:48:01Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a41ae57707f9efd1114689a427cffc92b7722139",
   "in_reply_to": null,
   "text": "I don't think this function needs `noexcept`."
  },
  {
   "t": "2026-09-08T16:15:19Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/logging.cpp",
   "commit": "a41ae57707f9efd1114689a427cffc92b7722139",
   "in_reply_to": null,
   "text": "This extra variable now seems unnecessary, no?"
  },
  {
   "t": "2026-09-08T16:18:32Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "a41ae57707f9efd1114689a427cffc92b7722139",
   "text": "Is the commit message stale? I'm having a hard time understanding the second paragraph."
  }
 ],
 "labels_log": [
  {
   "t": "2025-10-17T19:06:12Z",
   "action": "labeled",
   "label": "Utils/log/libs",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-02T14:51:06Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-02T17:15:20Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-24T10:48:50Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-15T10:59:10Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2025-10-17T19:07:47Z",
   "kind": "closed",
   "who": "cedwies"
  },
  {
   "t": "2025-10-17T19:08:47Z",
   "kind": "reopened",
   "who": "cedwies"
  }
 ],
 "text_chars": 4278,
 "text_tokens_estimate": 1069,
 "changed_paths": [
  "src/logging.cpp"
 ],
 "files": [
  {
   "path": "src/logging.cpp",
   "add": 29,
   "del": 8
  }
 ],
 "test_lines": 0,
 "git": {
  "head": "a41ae57707f9efd1114689a427cffc92b7722139",
  "head_matches_backup": true,
  "base": "7dea464d6b51a69bd99a0451be8aaf3a26313eb6",
  "commits": [
   {
    "sha": "a41ae57707",
    "subject": "log: check fclose() results and report safely",
    "files": 1,
    "add": 29,
    "del": 8
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "759cda9ad2b0824e",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}