Commit e165ffc1 authored by Kirill Smelkov's avatar Kirill Smelkov

bigfile/py: Teach loadblk() to automatically break reference cycles to pybuf

to avoid bugging on `pybuf->ob_refcnt != 1`when an exception was internally raised & caught somewhere in loadblk() implementation.

Details are in 9aa6a5d7, 61b18a40. The last patch also resorts to buffer unpinning when nothing helps (please see details about unpinning there).

Fixes: nexedi/wendelin.core#7

/cc @Tyagov @klaus @jm
/reviewed-on nexedi/wendelin.core!3
parents 41d4a4f8 024c246c
This diff is collapsed.
...@@ -125,22 +125,19 @@ def test_basic(): ...@@ -125,22 +125,19 @@ def test_basic():
# test that python exception state is preserved across pagefaulting # test that python exception state is preserved across pagefaulting
keepg = []
def test_pagefault_savestate(): def test_pagefault_savestate():
keep = []
class BadFile(BigFile): class BadFile(BigFile):
def loadblk(self, blk, buf): def loadblk(self, blk, buf):
# simulate some errors in-between to overwrite thread exception # simulate some errors in-between to overwrite thread exception
# state, and say we are done ok # state, and say we are done ok
try: try:
1/0 1/0
except ZeroDivisionError: except:
pass exc_type, exc_value, exc_traceback = sys.exc_info()
exc_type, exc_value, exc_traceback = sys.exc_info() assert exc_type is ZeroDivisionError
if PY2:
assert exc_type is ZeroDivisionError
else:
# on python3 exception state is cleared upon exiting from `except`
assert exc_type is None
# NOTE there is a loop created here: # NOTE there is a loop created here:
...@@ -151,18 +148,41 @@ def test_pagefault_savestate(): ...@@ -151,18 +148,41 @@ def test_pagefault_savestate():
# v .f_localsplus # v .f_localsplus
# frame # frame
# #
# Since upon returning we can't hold a reference to buf, let's # which result in holding additional ref to buf, but loadblk caller
# break the loop explicitly. # will detect and handle this situation via garbage-collecting
# # above cycle.
# Otherwise both exc_traceback and frame will be alive until next
# gc.collect() which cannot be perform in pagefault handler. # and even if we keep traceback alive it will care to detach buf
# # from frame via substituting another stub object inplace of it
# Not breaking this loop will BUG with `buf.refcnt != 1` on return exc_traceback.tb_frame.f_locals
del exc_traceback keep.append(exc_traceback)
# check same when happenned in function one more level down
self.func(buf)
# a case where only f_locals dict is kept alive
self.keep_f_locals(buf)
self.loadblk_run = 1 self.loadblk_run = 1
def func(self, arg):
try:
1/0
except:
_, _, exc_traceback = sys.exc_info()
assert exc_traceback is not None
keep.append(exc_traceback)
@staticmethod
def keep_f_locals(arg):
try:
1/0
except:
keepg.append(sys.exc_info()[2].tb_frame.f_locals)
f = BadFile(PS) f = BadFile(PS)
fh = f.fileh_open() fh = f.fileh_open()
vma = fh.mmap(0, 1) vma = fh.mmap(0, 1)
...@@ -184,6 +204,9 @@ def test_pagefault_savestate(): ...@@ -184,6 +204,9 @@ def test_pagefault_savestate():
assert exc_value is exc_value2 assert exc_value is exc_value2
assert exc_tb is exc_tb2 assert exc_tb is exc_tb2
assert keep[0].tb_frame.f_locals['buf'] == "<pybuf>" # the stub object
assert keep[1].tb_frame.f_locals['arg'] == "<pybuf>" # ----//----
# TODO close f # TODO close f
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment