fix: flush asynchronous JSONL logs on training end - #9710
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces proper lifecycle management for JsonlWriter by adding flush and close methods, implementing asynchronous closing with a worker thread sentinel, and raising errors when appending to a closed writer. It also updates the training callback to close the writer at the end of training and adds corresponding unit tests. The review feedback suggests several robustness improvements: adding a null check for self.jsonl_writer in the callback to prevent potential AttributeError crashes, preserving only the first background worker exception to avoid overwriting the root cause, and checking for worker exceptions during append to enable fail-fast behavior.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| self.jsonl_writer = JsonlWriter(logging_path, enable_async=True, write_on_rank='last') | ||
|
|
||
| def on_train_end(self): | ||
| self.jsonl_writer.close() |
There was a problem hiding this comment.
If on_train_begin fails or is not called before on_train_end, self.jsonl_writer will remain None, causing an AttributeError when calling close(). Adding a null check prevents this potential crash during cleanup.
| self.jsonl_writer.close() | |
| if self.jsonl_writer is not None: | |
| self.jsonl_writer.close() |
| except Exception as e: | ||
| self._worker_exception = e |
There was a problem hiding this comment.
Storing only the first exception that occurs in the background worker thread is a best practice. This ensures that the root cause of the failure is preserved and reported, rather than being overwritten by subsequent errors.
| except Exception as e: | |
| self._worker_exception = e | |
| except Exception as e: | |
| if self._worker_exception is None: | |
| self._worker_exception = e |
| if self._closed: | ||
| raise RuntimeError('Cannot append to a closed JsonlWriter') |
There was a problem hiding this comment.
Checking self._worker_exception inside append allows the writer to fail-fast. If a background write fails (e.g., due to disk full), subsequent calls to append will raise the exception immediately rather than silently queueing items in memory until flush or close is called.
if self._closed:
raise RuntimeError('Cannot append to a closed JsonlWriter')
if self._worker_exception is not None:
raise self._worker_exception
What does this PR do?
flush()andclose()lifecycle methods toJsonlWriter.Motivation
Megatron writes
logging.jsonlthrough a daemon thread. The writer was not flushed or closed when training finished, so the process could exit before pending log entries were written, resulting in incomplete logging files.Tests
test_async_jsonl_closeto verify that all queued records are persisted beforeclose()returns.RuntimeError.git diff --check.