Skip to content

select.poll is not thread safe #53111

Closed
Closed

Description

@apexo
mannequin
BPO 8865
Nosy @gpshead, @pitrou, @vstinner, @asvetlov, @serhiy-storchaka
Files
  • select_threads.py: python2 script that demonstrates the problem
  • concurrent_poll_and_register.py: python2 script to demonstrate memory corruption issue
  • select.patch: a patch
  • select_concurrent_poll.patch: Updated to 2.7 for review patch
  • select_concurrent_poll.patch
  • issue8865-poll-multithread-gps01.diff: updated, against 3.2
  • issue8865_v2.diff: forbid concurrent poll() invocation
  • issue8865_v3.diff: RuntimeError instead of TypeError
  • test.py: crasher
  • issue8865_test.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/serhiy-storchaka'
    closed_at = <Date 2013-08-20.18:23:29.244>
    created_at = <Date 2010-05-31.22:20:13.881>
    labels = ['library', 'type-crash']
    title = 'select.poll is not thread safe'
    updated_at = <Date 2013-08-20.18:23:29.243>
    user = 'https://bugs.python.org/apexo'

    bugs.python.org fields:

    activity = <Date 2013-08-20.18:23:29.243>
    actor = 'serhiy.storchaka'
    assignee = 'serhiy.storchaka'
    closed = True
    closed_date = <Date 2013-08-20.18:23:29.244>
    closer = 'serhiy.storchaka'
    components = ['Library (Lib)']
    creation = <Date 2010-05-31.22:20:13.881>
    creator = 'apexo'
    dependencies = []
    files = ['17510', '17511', '17675', '27961', '27963', '27964', '27967', '27968', '28687', '28855']
    hgrepos = []
    issue_num = 8865
    keywords = ['patch']
    message_count = 31.0
    messages = ['106815', '106816', '106827', '106829', '107863', '175387', '175389', '175390', '175394', '175395', '175397', '175400', '175409', '175429', '175431', '175432', '175501', '175525', '176894', '176895', '176969', '176998', '179657', '179659', '180696', '181822', '181823', '186736', '194334', '195705', '195707']
    nosy_count = 9.0
    nosy_names = ['gregory.p.smith', 'exarkun', 'pitrou', 'vstinner', 'asvetlov', 'neologix', 'apexo', 'python-dev', 'serhiy.storchaka']
    pr_nums = []
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'crash'
    url = 'https://bugs.python.org/issue8865'
    versions = ['Python 2.7', 'Python 3.3', 'Python 3.4']

    Activity

    1. apexo commented on May 31, 2010

      apexomannequin
      MannequinAuthor

      invoking select.poll.poll() concurrently from multiple threads frequently yields garbage in one thread:

      while poll_poll in thread 1 is parsing its result, another thread 2 calling poll may overwrite revents; assuming poll_result was 1 in thread 1 and thread 2 managed to clear all revents before thread 1 started scanning ufds, thread 1 would iterate straight through all ufds, past its bounds (no bound checks there), and return the first out-of-bounds entry that happens to have revents != 0

      this issue needs at least documentation (although bounds-checking to prevent garbage in the result wouldn't hurt)

      also, since there doesn't seem to be any locking w/ regards to ufds, it might be possible to corrupt python's heap, by concurrently invoking poll_register and poll_poll. poll_register could move the ufds array to another location while resizing it and poll_poll would subsequently overwrite memory that is not allocated anymore or allocated by someone else (did not test that)

      python 2.5.5

    2. added
      docsDocumentation in the Doc dir
      type-bugAn unexpected behavior, bug, or error
      stdlibStandard Library Python modules in the Lib/ directory
      on May 31, 2010
    3. pitrou commented on May 31, 2010

      @pitrou
      Member

      Do you have a script that reproduces it?

    4. removed
      docsDocumentation in the Doc dir
      on May 31, 2010
    5. apexo commented on Jun 1, 2010

      apexomannequin
      MannequinAuthor

      okay, I've managed to put together a script that fairly consistently reproduces the problem on my (core2 duo 2,something GHz) machine

      some parameters (sleep times) are randomized in each iteration, the script runs for 30 iterations, each for 1 second, and counts the number of events where the fd looks bogus (max 1 per iteration); some other events (fd correct, revents bogus are also reported, but not counted)

      example output:
      poll returned [(-3, 65535)], we were looking for fd 3
      poll returned [(-3, 65535)], we were looking for fd 3
      poll returned [(-3, 65535)], we were looking for fd 3
      poll returned [(-3, 65535)], we were looking for fd 3
      poll returned [(-3, 65535)], we were looking for fd 3
      poll returned [(-1, 65535)], we were looking for fd 3
      poll returned [(-1, 65535)], we were looking for fd 3
      poll returned [(-1627358347, 60497)], we were looking for fd 3
      should not get here; poll returned [(3, 0)]
      poll returned [(-3, 65535)], we were looking for fd 3
      9 events in 30 iterations

    6. apexo commented on Jun 1, 2010

      apexomannequin
      MannequinAuthor

      the other issue (poll_register freeing data structures that poll_poll currently uses) can be reproduced by the attached script (at least I can)

      depending on the parameters/circumstances it either segfaults or prints garbage results from poll, e.g. [(1684955474, 28192)]; the expected result would be [(3, 1)]

    7. apexo commented on Jun 15, 2010

      apexomannequin
      MannequinAuthor

      added a patch which fixes both issues

      before releasing the GIL we take a copy of the ufds pointer and its len, erasing the ufds pointer in the poll object (to make sure nobody else fiddles with it); when we're done we either but it back into the object (when it's still NULL) or we free it

      the update logic is modified as well, since the uptodate flag is not sufficient anymore, we now count up a tag for each modification of the poll dictionary and remember that tag together with the ufds pointer

      the result is:

      • for the single-threaded case we do little extra work (moving ufds from/to poll object)
      • correct for the multi-threaded case, with slightly higher overhead (one additional call to each of malloc, update_ufd_array, free), probably worse than having one poll object per thread, but not worse than allocating a new poll object each time we want to poll

      there still is potential for incorrect poll results (but not for memory corruption): when poll_register/poll_unregister are called exactly 2**32 times (or multiples thereof) while poll_poll is running, it will happily put back its outdated ufds pointer into the poll object when its done, this could be alleviated by changing tag to long long ... which is unlikely to wrap around anytime soon.

    8. self-assigned this
      on Jun 7, 2012
    9. removed their assignment
      on Nov 11, 2012
    10. serhiy-storchaka commented on Nov 11, 2012

      @serhiy-storchaka
      Member

      I was unable to apply the patch automatically, so I had to do it manually. Here is an updated patch for review. I did not consider it in detail yet, but it seems to correct these errors.

    11. 17 remaining items

    12. serhiy-storchaka commented on Dec 5, 2012

      @serhiy-storchaka
      Member

      Test should check changed behavior. I.e. it should test that calling poll concurrently raises an exception.

    13. neologix commented on Jan 11, 2013

      neologixmannequin
      Mannequin

      This patch should be updated to also fix epoll().

      Also, is it right to raise an exception in case of concurrent invocation?

      Here's a simple script that crashes systematically on my Linux box.

    14. serhiy-storchaka commented on Jan 11, 2013

      @serhiy-storchaka
      Member

      Also, is it right to raise an exception in case of concurrent invocation?

      It is right for poll() because it was not concurrently usable in previous versions in any case. For epoll() it is an another issue.

    15. serhiy-storchaka commented on Jan 26, 2013

      @serhiy-storchaka
      Member

      Here is a test made from Charles-François's crasher. Let's go.

    16. serhiy-storchaka commented on Feb 10, 2013

      @serhiy-storchaka
      Member

      Ping.

    17. pitrou commented on Feb 10, 2013

      @pitrou
      Member

      Patches look good to me.

    18. serhiy-storchaka commented on Apr 13, 2013

      @serhiy-storchaka
      Member

      Gregory?

    19. serhiy-storchaka commented on Aug 4, 2013

      @serhiy-storchaka
      Member

      Gregory, could you commit the patch or allow me to do this?

    20. python-dev commented on Aug 20, 2013

      python-devmannequin
      Mannequin

      New changeset 072ba5df77e4 by Serhiy Storchaka in branch '3.3':
      Issue bpo-8865: Concurrent invocation of select.poll.poll() now raises a
      http://hg.python.org/cpython/rev/072ba5df77e4

      New changeset 4543408e2ba6 by Serhiy Storchaka in branch 'default':
      Issue bpo-8865: Concurrent invocation of select.poll.poll() now raises a
      http://hg.python.org/cpython/rev/4543408e2ba6

      New changeset a4091c1de27a by Serhiy Storchaka in branch '2.7':
      Issue bpo-8865: Concurrent invocation of select.poll.poll() now raises a
      http://hg.python.org/cpython/rev/a4091c1de27a

    21. serhiy-storchaka commented on Aug 20, 2013

      @serhiy-storchaka
      Member

      Thank you for the report and the patch Christian.

    22. transferred this issue fromon Apr 10, 2022
    23. added a commit that references this issue on Jul 24, 2025
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions