cPyMemTrace Thread Safety
pymemtrace.cPyMemTrace up to and including version 0.6 is not thread safe.
The Problem
The CPython runtime can only have one Profile/Trace/Reference Trace callback function at any time. pymemtrace.cPyMemTrace gets over this limitation where multiple tracers are required by maintaining a linked list of callbacks. When a new one is created it is pushed onto the head of the list and takes precedence. When it goes out of scope it is popped from the list and the older one is re-registered.
For example:
from pymemtrace import cPyMemTrace
with cPyMemTrace.ReferenceTracing(filepath='A.log'):
# Now writing to "A.log"
with cPyMemTrace.ReferenceTracing(filepath='B.log'):
# Writing to "A.log" is suspended.
# Now writing to "B.log"
pass
# The log file "B.log" is closed.
# Writing to "A.log" is resumed.
pass
# The log file "A.log" is closed.
This works fine for a single threaded process. However with multiple threads this fails.
Introducing Threading
Supposing that we have this code:
import threading
from pymemtrace import cPyMemTrace
def do_work(some_argument):
with cPyMemTrace.ReferenceTracing() as profiler:
profiler.write_message_to_log(f'Starting: {some_argument}')
# Do some work...
threads = [
threading.Thread(target=do_work, args=('Thread-A',),),
threading.Thread(target=do_work, args=('Thread-B',),),
]
# Start each thread
for t in threads:
t.start()
# Wait for all threads to finish
for t in threads:
t.join()
Thread A will create a log file, say "A.log" (log file names are simplified here for clarity). The linked list of Reference Tracers will look like this:
Head Node
|
File "A.log" ---> NULL
Then thread B creates a log file, say "B.log" The linked list of Reference Tracers now looks like this:
Head Node
|
File "B.log" ---> File "A.log" ---> NULL
Now both threads are writing events to "B.log".
Suppose thread A completes first it will pop off the head node closing the file "B.log". Then the linked list will look like this:
Head Node
|
File "A.log" ---> NULL
And, as thread B is still running, it is writing events to the file meant for thread A.
So both log files are corrupt:
- "A.log" contains leading events from thread A and trailing events from thread B.
- "B.log" has events from thread A and thread B.
Compelling a Common Log File
One solution might be that all treads write to a common, named, log file like this:
import threading
from pymemtrace import cPyMemTrace
def do_work(some_argument):
with cPyMemTrace.ReferenceTracing(filepath='do_work.log') as profiler:
profiler.write_message_to_log(f'Starting: {some_argument}')
# Do some work...
threads = [
threading.Thread(target=do_work, args=('Thread-A',),),
threading.Thread(target=do_work, args=('Thread-B',),),
]
# Start each thread
for t in threads:
t.start()
# Wait for all threads to finish
for t in threads:
t.join()
Now both threads will have a separate FILE * to the same output stream but those FILE * handles can be in a different state. In particular the write pointer (found by tell()) can be different. Both FILE * handles will have a write pointer less than or equal to the file length so there is no chance of writing beyond that. However each thread can overwrite the other's output.
Solution
The complete solution to this is proposed elsewhere.
cPyMemTraceThread Safetypymemtrace.cPyMemTraceup to and including version 0.6 is not thread safe.The Problem
The CPython runtime can only have one Profile/Trace/Reference Trace callback function at any time.
pymemtrace.cPyMemTracegets over this limitation where multiple tracers are required by maintaining a linked list of callbacks. When a new one is created it is pushed onto the head of the list and takes precedence. When it goes out of scope it is popped from the list and the older one is re-registered.For example:
This works fine for a single threaded process. However with multiple threads this fails.
Introducing Threading
Supposing that we have this code:
Thread A will create a log file, say "A.log" (log file names are simplified here for clarity). The linked list of Reference Tracers will look like this:
Then thread B creates a log file, say "B.log" The linked list of Reference Tracers now looks like this:
Now both threads are writing events to "B.log".
Suppose thread A completes first it will pop off the head node closing the file "B.log". Then the linked list will look like this:
And, as thread B is still running, it is writing events to the file meant for thread A.
So both log files are corrupt:
Compelling a Common Log File
One solution might be that all treads write to a common, named, log file like this:
Now both threads will have a separate
FILE *to the same output stream but thoseFILE *handles can be in a different state. In particular the write pointer (found bytell()) can be different. BothFILE *handles will have a write pointer less than or equal to the file length so there is no chance of writing beyond that. However each thread can overwrite the other's output.Solution
The complete solution to this is proposed elsewhere.