Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "c2pa-python"
version = "0.34.0"
version = "0.35.0"
requires-python = ">=3.10"
description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library"
readme = { file = "README.md", content-type = "text/markdown" }
Expand Down
12 changes: 11 additions & 1 deletion src/c2pa/c2pa.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from pathlib import Path
from typing import Optional, Union, Callable, Any, overload
import io
from .lib import dynamically_load_library
from .lib import dynamically_load_library, record_owner_pid, is_foreign_process
import mimetypes
from itertools import count

Expand Down Expand Up @@ -238,6 +238,7 @@ def __init__(self):
self._lifecycle_state = LifecycleState.UNINITIALIZED
self._handle = None
_clear_error_state()
record_owner_pid(self)

@staticmethod
def _free_native_ptr(ptr):
Expand Down Expand Up @@ -280,6 +281,8 @@ def _mark_consumed(self):
def _cleanup_resources(self):
"""Release native resources idempotently."""
try:
if is_foreign_process(self):
return
if (
hasattr(self, '_lifecycle_state')
and self._lifecycle_state != LifecycleState.CLOSED
Expand Down Expand Up @@ -1768,6 +1771,7 @@ def flush_callback(ctx):
raise Exception("Failed to create stream: {}".format(error))

self._initialized = True
record_owner_pid(self)

def __enter__(self):
"""Context manager entry."""
Expand All @@ -1786,6 +1790,8 @@ def __del__(self):
hasn't been explicitly closed.
"""
try:
if is_foreign_process(self):
return
# Only cleanup if not already closed and we have a valid stream
if hasattr(self, '_closed') and not self._closed:
stream = self._stream
Expand Down Expand Up @@ -1816,6 +1822,10 @@ def close(self):
"""
if self._closed:
return
if is_foreign_process(self):
self._closed = True
self._initialized = False
return

try:
# Clean up stream first as it depends on callbacks
Expand Down
25 changes: 25 additions & 0 deletions src/c2pa/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,28 @@ def dynamically_load_library(
raise RuntimeError(f"Could not find {c2pa_lib_name} in any of the search paths")

return c2pa_lib


def record_owner_pid(obj):
"""Keep the PID that created this native-handle wrapper
(call from __init__ as needed).
"""
obj._owner_pid = os.getpid()


def is_foreign_process(obj):
"""Return True when this object is being finalized in a forked child that did not
create it. After a multithreaded fork(), native mutexes may be held by threads
absent in the child -> any lock() call deadlocks. Callers must skip native frees
when this returns True.

Skipping the free does not cause a cumulative leak. If the child calls exec() the
address space is replaced entirely; if it exits, the OS reclaims all process memory.
Even a long-lived fork child (e.g. a multiprocessing fork-start worker) leaks at most
the objects inherited at fork time — a one-time, bounded amount reclaimed when the
child terminates. Objects the child creates itself carry the child's PID and are
freed normally.

Defensive default: if _owner_pid was never set, returns False (no regression)."""
owner = getattr(obj, '_owner_pid', None)
return owner is not None and owner != os.getpid()
213 changes: 124 additions & 89 deletions tests/perf/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,154 +2,189 @@
"_meta": {
"memray_version": "1.19.3",
"python_version": "3.12.13",
"c2pa_native_version": "c2pa-v0.86.1",
"iterations": 100,
"c2pa_native_version": "c2pa-v0.88.0",
"iterations": 250,
"perf_env": "python-3.12-slim",
"arch": "aarch64"
},
"reader_jpeg_legacy": {
"peak_bytes": 3730321,
"leaked_bytes": 3236992,
"total_allocations": 717596
"peak_bytes": 3762136,
"leaked_bytes": 3262031,
"total_allocations": 1660100
},
"reader_jpeg_with_context": {
"peak_bytes": 3724412,
"leaked_bytes": 3229219,
"total_allocations": 711543
"peak_bytes": 3756118,
"leaked_bytes": 3254069,
"total_allocations": 1646550
},
"reader_mp4": {
"peak_bytes": 4099225,
"leaked_bytes": 3228160,
"total_allocations": 2084373
"peak_bytes": 4208976,
"leaked_bytes": 3253426,
"total_allocations": 4963836
},
"reader_wav": {
"peak_bytes": 4399719,
"leaked_bytes": 3238102,
"total_allocations": 408059
"peak_bytes": 4431268,
"leaked_bytes": 3263354,
"total_allocations": 888612
},
"builder_sign_jpeg_legacy": {
"peak_bytes": 7663441,
"leaked_bytes": 3352658,
"total_allocations": 555922
"peak_bytes": 7694334,
"leaked_bytes": 3377383,
"total_allocations": 1250156
},
"builder_sign_jpeg_with_context": {
"peak_bytes": 7656560,
"leaked_bytes": 3345863,
"total_allocations": 550105
"peak_bytes": 7687683,
"leaked_bytes": 3370546,
"total_allocations": 1235555
},
"builder_sign_png_legacy": {
"peak_bytes": 7900956,
"leaked_bytes": 3351994,
"total_allocations": 1978914
"peak_bytes": 7933540,
"leaked_bytes": 3377209,
"total_allocations": 4502325
},
"builder_sign_png_with_context": {
"peak_bytes": 7893973,
"leaked_bytes": 3345450,
"total_allocations": 1973003
"peak_bytes": 7924904,
"leaked_bytes": 3370312,
"total_allocations": 4487743
},
"builder_sign_jpeg_parallel_split_pool": {
"peak_bytes": 45726143,
"leaked_bytes": 3714796,
"total_allocations": 557891
"peak_bytes": 45790186,
"leaked_bytes": 3770876,
"total_allocations": 1247400
},
"builder_sign_jpeg_parallel_split_barrier": {
"peak_bytes": 45817488,
"leaked_bytes": 3780629,
"total_allocations": 627768
"peak_bytes": 45758269,
"leaked_bytes": 3769617,
"total_allocations": 1246063
},
"builder_sign_png_parallel_split_pool": {
"peak_bytes": 40563556,
"leaked_bytes": 3746819,
"total_allocations": 1984928
"peak_bytes": 42884934,
"leaked_bytes": 3806845,
"total_allocations": 4499456
},
"builder_sign_png_parallel_split_barrier": {
"peak_bytes": 45964496,
"leaked_bytes": 3745249,
"total_allocations": 1983686
"peak_bytes": 45995848,
"leaked_bytes": 3805431,
"total_allocations": 4498202
},
"builder_sign_gif": {
"peak_bytes": 14514114,
"leaked_bytes": 3345545,
"total_allocations": 8547131
"peak_bytes": 14545704,
"leaked_bytes": 3369959,
"total_allocations": 19592169
},
"builder_sign_heic": {
"peak_bytes": 7717414,
"leaked_bytes": 3381771,
"total_allocations": 877927
"peak_bytes": 4609675,
"leaked_bytes": 3369960,
"total_allocations": 1865279
},
"builder_sign_m4a": {
"peak_bytes": 18817771,
"leaked_bytes": 3345503,
"total_allocations": 2627261
"peak_bytes": 18848657,
"leaked_bytes": 3369911,
"total_allocations": 6143845
},
"builder_sign_webp": {
"peak_bytes": 8869451,
"leaked_bytes": 3345563,
"total_allocations": 496534
"peak_bytes": 8901476,
"leaked_bytes": 3369960,
"total_allocations": 1108971
},
"builder_sign_avi": {
"peak_bytes": 7009007,
"leaked_bytes": 3345266,
"total_allocations": 45029611
"peak_bytes": 7041162,
"leaked_bytes": 3369959,
"total_allocations": 105383089
},
"builder_sign_mp4": {
"peak_bytes": 6131977,
"leaked_bytes": 3345600,
"total_allocations": 1923444
"peak_bytes": 6163626,
"leaked_bytes": 3369959,
"total_allocations": 4502723
},
"builder_sign_tiff": {
"peak_bytes": 13091168,
"leaked_bytes": 3345348,
"total_allocations": 5469122
"peak_bytes": 13123408,
"leaked_bytes": 3369960,
"total_allocations": 13221796
},
"builder_sign_jpeg_parent_of": {
"peak_bytes": 14143351,
"leaked_bytes": 3345698,
"total_allocations": 1285766
"peak_bytes": 14175499,
"leaked_bytes": 3370412,
"total_allocations": 3049271
},
"builder_sign_jpeg_component_of": {
"peak_bytes": 14144869,
"leaked_bytes": 3345779,
"total_allocations": 1308244
"peak_bytes": 14176177,
"leaked_bytes": 3370939,
"total_allocations": 3105680
},
"builder_sign_jpeg_parent_and_component": {
"peak_bytes": 14434957,
"leaked_bytes": 3450621,
"total_allocations": 2289962
"peak_bytes": 14521625,
"leaked_bytes": 3466959,
"total_allocations": 5528187
},
"builder_sign_jpeg_parent_and_component_mixed_mime": {
"peak_bytes": 14445750,
"leaked_bytes": 3345959,
"total_allocations": 2787986
"peak_bytes": 14476222,
"leaked_bytes": 3370421,
"total_allocations": 6500129
},
"builder_sign_jpeg_two_components_same_mime": {
"peak_bytes": 14432257,
"leaked_bytes": 3442393,
"total_allocations": 2279745
"peak_bytes": 14584523,
"leaked_bytes": 3506901,
"total_allocations": 5502714
},
"builder_sign_jpeg_two_components_mixed_mime": {
"peak_bytes": 14443122,
"leaked_bytes": 3346165,
"total_allocations": 2777653
"peak_bytes": 14473585,
"leaked_bytes": 3370669,
"total_allocations": 6474343
},
"builder_sign_jpeg_archive_roundtrip": {
"peak_bytes": 14175766,
"leaked_bytes": 3365189,
"total_allocations": 1767740
"peak_bytes": 14206327,
"leaked_bytes": 3389587,
"total_allocations": 4247160
},
"reader_error_no_manifest": {
"peak_bytes": 3443163,
"leaked_bytes": 3207515,
"total_allocations": 173242
"peak_bytes": 3474039,
"leaked_bytes": 3232411,
"total_allocations": 303795
},
"builder_error_invalid_manifest": {
"peak_bytes": 3243627,
"leaked_bytes": 3186717,
"total_allocations": 95461
"peak_bytes": 3271093,
"leaked_bytes": 3211670,
"total_allocations": 120613
},
"reader_string_apis": {
"peak_bytes": 3857136,
"leaked_bytes": 3229581,
"total_allocations": 1178409
"peak_bytes": 3888863,
"leaked_bytes": 3254426,
"total_allocations": 2806719
},
"fork_reader_collect": {
"peak_bytes": 3760272,
"leaked_bytes": 3261839,
"total_allocations": 1615850
},
"fork_contended_mutex": {
"peak_bytes": 7585897,
"leaked_bytes": 3392170,
"total_allocations": 82616370
},
"fork_thread_local_orphan": {
"peak_bytes": 3845601,
"leaked_bytes": 3348583,
"total_allocations": 1681204
},
"fork_gc_cycle": {
"peak_bytes": 3760522,
"leaked_bytes": 3261588,
"total_allocations": 1620079
},
"fork_parent_frees_after_fork": {
"peak_bytes": 5959081,
"leaked_bytes": 3260442,
"total_allocations": 30560082
},
"fork_child_sys_exit": {
"peak_bytes": 3760808,
"leaked_bytes": 3261849,
"total_allocations": 1624602
},
"fork_stream_cleanup": {
"peak_bytes": 3382176,
"leaked_bytes": 3210242,
"total_allocations": 111383
}
}
Loading
Loading