This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: multiprocessing pickle error
Type: Stage:
Components: Versions: Python 3.6
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: cxss, davin, serhiy.storchaka, vinay.sajip
Priority: normal Keywords:

Created on 2017-01-05 12:12 by cxss, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
sample.py cxss, 2017-01-05 13:39
Messages (10)
msg284738 - (view) Author: Simon Schuler (cxss) Date: 2017-01-05 12:12
Hello,

the following code doesn't work any longer in the new Python version 3.6.

import sys
import os
import subprocess
from multiprocessing import Pool, Value, Queue
import multiprocessing
import logging
import logging.handlers
import pickle

queue = multiprocessing.Manager().Queue(-1)
qh = logging.handlers.QueueHandler(queue)
pickle.dumps(qh)

It raises the following exception.
>>> TypeError: can't pickle _thread.RLock objects


Furthermore, also for customized logging handler classes it doesn't work anymore.

class CustomHandler(logging.Handler):
    def __init__(self, queue):
        logging.Handler.__init__(self)
        self.queue = queue
        
    def emit(self, record):
        try:
            ei = record.exc_info
            if ei:
                dummy = self.format(record)
                record.exc_info = None  
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

For a centralized logging facility in a multiprocess environment this is a big problem. How can I handle this in the 3.6 version?
msg284744 - (view) Author: Simon Schuler (cxss) Date: 2017-01-05 13:39
Attached is a sample program to illustrate the problem. When I use a multiprocessing pool the exception is raised.
msg284746 - (view) Author: Davin Potts (davin) * (Python committer) Date: 2017-01-05 13:47
In your first example (not sample.py), if I make the following change:

#queue = multiprocessing.Manager().Queue(-1)
queue = 42


I am still able to reproduce the exception you observe.  This suggests the issue has nothing to do with the use of multiprocessing but does have to do with the logging handlers.

I can also reproduce that this is a change in behavior between 3.5 and 3.6.
msg284748 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2017-01-05 14:59
It may be that _thread.RLock objects were pickleable before 3.6 but aren't now, and if so, then this is unrelated to logging except by coincidence.

Why do you want to pickle the *handler*? It's not especially designed for sending over the wire, and that's not supported - so it's not a bug. If that code worked in Python < 3.6, that's just by luck.

Pickling of LogRecords should work.
msg284750 - (view) Author: Simon Schuler (cxss) Date: 2017-01-05 15:23
I want to handle the logging of the main and all my started processes. They should all log to the same Queue. 

Have a look at the sample.py program. In addition there is a inconsistency in using a multiprocessing pool or just the process class directly. The sample program illustrated this.
msg284751 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-01-05 15:33
_thread.RLock objects were wrongly pickleable before 3.6.

>>> import pickle, pickletools, _thread
>>> lock = _thread.RLock()
>>> lock.acquire()
True
>>> lock
<locked _thread.RLock object owner=-1219508480 count=1 at 0xb713acb0>
>>> pickle.loads(pickle.dumps(lock))
<unlocked _thread.RLock object owner=0 count=0 at 0xb6facb90>
>>> pickletools.dis(pickletools.optimize(pickle.dumps(lock)))
    0: \x80 PROTO      3
    2: c    GLOBAL     '_thread RLock'
   17: )    EMPTY_TUPLE
   18: \x81 NEWOBJ
   19: .    STOP
highest protocol among opcodes = 2

Actually only an information about the type was saved. The state of the lock was lost. Unpickled lock was not a copy of the original lock, but a newly created object with default initial state.

There were errors in programs that pickled _thread.RLock objects. Just these errors were unnoticed. But programs likely didn't work as expected. Now an exception raised on early stage allows you to rewrite not working code. I don't know whether QueueHandler was designed to be pickleable. As a workaround try to set the lock attribute to None before pickling and set it to threading.RLock() (or call the createLock() method) after unpickling.

This change was made in issue22995.
msg284753 - (view) Author: Simon Schuler (cxss) Date: 2017-01-05 15:46
I don't have any lock object. I just use the multiprocessing pool and a QueueHandler in order to be able to log from all processes.
msg284755 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2017-01-05 15:54
> Have a look at the sample.py program.

It fails on Python 3.4.3 on my Linux system (just renamed to mp_sample.py):

python3 mp_sample.py 
starting (without multiprocessing pool)...
worker process 0
worker process 3
worker process 4
worker process 6
worker process 5
worker process 9
worker process 8
worker process 7
worker process 1
worker process 2
starting (with multiprocessing pool)...
Traceback (most recent call last):
  File "mp_sample.py", line 82, in <module>
    op.start()
  File "mp_sample.py", line 43, in start
    result.get()
  File "/usr/lib/python3.4/multiprocessing/pool.py", line 599, in get
    raise self._value
  File "/usr/lib/python3.4/multiprocessing/pool.py", line 383, in _handle_tasks
    put(task)
  File "/usr/lib/python3.4/multiprocessing/connection.py", line 206, in send
    self._send_bytes(ForkingPickler.dumps(obj))
  File "/usr/lib/python3.4/multiprocessing/reduction.py", line 50, in dumps
    cls(buf, protocol).dump(obj)
  File "/usr/lib/python3.4/multiprocessing/queues.py", line 57, in __getstate__
    context.assert_spawning(self)
  File "/usr/lib/python3.4/multiprocessing/context.py", line 347, in assert_spawning
    ' through inheritance' % type(obj).__name__
RuntimeError: Queue objects should only be shared between processes through inheritance

Also:

QueueHandler isn't designed to be pickleable, nor is that necessary for use with multiprocessing. The logging cookbook contains a working example with QueueHandler and multiple processes using multiprocessing, and I've just run it on Python 3.6 (and 3.7) with no problems. See

https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes

No idea why the sample program isn't working, and I'm sorry I haven't the time to look at it in detail. Please confirm if the linked sample works on your system, and if it does, see if you can adapt it to your needs. Note that it doesn't pickle any handlers.
msg284756 - (view) Author: Davin Potts (davin) * (Python committer) Date: 2017-01-05 15:56
Simon:  You do have a lock object inside the logging handler object.  That is what prevents your pickling of the logging handler object.

The background information Serhiy shared is focused on why pickling a _thread.RLock object is problematic and unsupportable.

I do not know enough to understand how you implemented your code that worked under 3.5 but one of the points made by Serhiy's comments is that your code worked _in spite_ of the fact that you did not have a proper RLock inside your logging handler.  I optimistically believe you can implement what you need to work without needing to pickle a logging handler object.
msg284830 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2017-01-06 16:08
I'm closing this on the basis that I don't believe there's a bug here, but you can of course re-open if you have evidence to the contrary.
History
Date User Action Args
2022-04-11 14:58:41adminsetgithub: 73354
2017-01-06 16:08:47vinay.sajipsetstatus: open -> closed
resolution: not a bug
messages: + msg284830
2017-01-05 15:56:08davinsetmessages: + msg284756
2017-01-05 15:54:43vinay.sajipsetmessages: + msg284755
2017-01-05 15:46:33cxsssetmessages: + msg284753
2017-01-05 15:33:58serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg284751
2017-01-05 15:23:30cxsssetmessages: + msg284750
2017-01-05 14:59:43vinay.sajipsetmessages: + msg284748
2017-01-05 13:50:51davinsetnosy: + vinay.sajip
2017-01-05 13:47:56davinsetnosy: + davin
messages: + msg284746
2017-01-05 13:39:54cxsssetfiles: + sample.py

messages: + msg284744
2017-01-05 12:12:48cxsscreate