T-17985 Handle RuntimeError from Thread.is_alive() on Python 3.14+#39
Merged
Conversation
Starting with Python 3.14, calling `Thread.is_alive()` on an already-terminated thread raises `RuntimeError` instead of returning `False`. The flush worker checks `self.parent_thread.is_alive()` to detect parent-thread shutdown, so on Python 3.14.x this surfaces as an uncaught `RuntimeError` during teardown. Wrap the check in a `_is_parent_alive()` helper that treats a `RuntimeError` as "no longer alive", and add regression tests. Fixes T-17985. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Python 3.7-3.9 are end-of-life, and the older matrix was failing. Bump the CI matrix to 3.10-3.14 on ubuntu-24.04 and add a workflow_dispatch trigger so the suite can be run on demand. Keep the out-of-CI support declarations in sync: update the tox envlist and gh-actions mapping, refresh the setup.py Python classifiers, and add `python_requires='>=3.10'` to make the dropped EOL versions explicit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the minimum supported version now Python 3.10, the `version_info` early-return in test_frame and the `skipIf(sys.version_info < (3, 8))` guard in test_flusher are always false/never-skip. Remove them and the now-unused `sys` / `version_info` imports. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
datetime.utcfromtimestamp() is deprecated for removal. Use the timezone-aware datetime.fromtimestamp(ts, timezone.utc), which produces the identical UTC-aware value the assertion compares against. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'3.x' resolves to the newest released stable Python 3, so future minor versions are exercised as soon as they ship (today it tracks 3.14 alongside the pinned entry). Set fail-fast: false so each version's result is reported independently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update user-facing product references: example project comments and log messages, the example README prose, the flusher endpoint comment, and the setup.py package description. Code identifiers (LogtailHandler, LogtailContext, the logtail package), repo URLs, the license copyright, and the "Logtail is now part of Better Stack" announcement are left as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Release v0.4.0: Python 3.14 compatibility fix plus the drop of EOL Python 3.7-3.9 (backwards-incompatible support change -> minor bump). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With Python 3.14.x,
logtail0.3.4 raises an uncaughtRuntimeErrorfromFlushWorkerduring teardown:Starting with Python 3.14, calling
Thread.is_alive()on an already-terminated thread raises aRuntimeErrorinstead of simply returningFalseas it did in earlier versions. The flush worker relies onself.parent_thread.is_alive()to detect that the parent thread has exited so it can flush remaining events and shut down, so on 3.14.x this surfaces as an exception during interpreter/thread teardown.Fix
Wrap the check in a small
_is_parent_alive()helper that catchesRuntimeErrorand treats it as "parent no longer alive" (which is the correct interpretation — the exception only occurs once the thread has terminated). Both call sites instep()now go through the helper.Tests
Added two regression tests in
tests/test_flusher.py:test_is_parent_alive_handles_runtime_error—_is_parent_alive()returnsFalsewhenis_alive()raisesRuntimeError.test_shutdown_condition_when_parent_is_alive_raises—step()still flushes outstanding events and shuts down cleanly when the parent-alive check raises.Verified locally on Python 3.14.4 (the version from the report):
tests/test_flusher.pypasses (9 tests).Also manually verified end-to-end on Python 3.14.4: the example project runs clean through teardown (no
RuntimeError) and all six log levels are delivered to a live source (HTTP202, confirmed in the dashboard including the exception traceback).CI & supported versions
The old CI matrix (Python 3.7–3.11 on
ubuntu-22.04) was failing on the EOL interpreters. This PR modernizes it:ubuntu-24.04, plus a floating'3.x'entry that always tracks the latest stable release, and aworkflow_dispatchtrigger for on-demand runs.tox.inienvlist/gh-actionsmapping, thesetup.pyclassifiers, and a newpython_requires='>=3.10'.datetime.utcfromtimestamp()call.Housekeeping
setup.pydescription). Code identifiers (LogtailHandler, thelogtailpackage), repo URLs, the license copyright, and the "Logtail is now part of Better Stack" announcement are intentionally left unchanged.Release
Important
This PR is the v0.4.0 release. The version has been bumped to
0.4.0in bothsetup.pyandlogtail/__init__.py. It's a minor bump (not a 0.3.x patch) because dropping Python 3.7–3.9 support is a backwards-incompatible change. Tagv0.4.0after merge to publish.Fixes #38.
🤖 Generated with Claude Code