_bigfile.c 41.3 KB
Newer Older
1
/* Wendelin.bigfile | Python interface to memory/files
2
 * Copyright (C) 2014-2020  Nexedi SA and Contributors.
3 4 5 6 7 8 9
 *                          Kirill Smelkov <kirr@nexedi.com>
 *
 * This program is free software: you can Use, Study, Modify and Redistribute
 * it under the terms of the GNU General Public License version 3, or (at your
 * option) any later version, as published by the Free Software Foundation.
 *
 * You can also Link and Combine this program with other software covered by
10 11 12 13
 * the terms of any of the Free Software licenses or any of the Open Source
 * Initiative approved licenses and Convey the resulting work. Corresponding
 * source of such a combination shall include the source code for all other
 * software used.
14 15 16 17 18
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See COPYING file for full licensing terms.
19
 * See https://www.nexedi.com/licensing for rationale and options.
20 21 22
 */

/* Package _bigfile provides Python bindings to virtmem.
23
 * See _bigfile.h for package overview. */
24 25

/* _bigfile organization
26
 *
27
 * NOTE on refcounting - who holds who:
28
 *
29
 *  vma  ->  fileh  ->  file(*)
30 31 32
 *   ^         |
 *   +---------+
 *   fileh->mmaps (kind of weak)
33
 *
34 35 36
 * (*) PyBigFile is intended to be used as subclass, whose children can add
 *     references from file to other objects. In particular ZBigFile keeps
 *     references to fileh that were created through it.
37 38 39 40 41 42 43
 *
 * NOTE virtmem/bigfile functions release/reacquire GIL (see virt_lock()) -
 * thus functions that use them cannot assume they run mutually exclusive to
 * other Python threads. See Py_CLEAR documentation which explain what could go
 * wrong if code is not careful to protect itself against concurrent GC:
 *
 * https://github.com/python/cpython/blob/v2.7.15-310-g112e4afd582/Include/object.h#L790-L798
44 45
 */

46
#include "bigfile/_bigfile.h"
47
#include "structmember.h"
48
#include "frameobject.h"
49 50 51 52

#include <wendelin/bigfile/ram.h>
#include <wendelin/bug.h>
#include <wendelin/compat_py2.h>
53
#include <ccan/container_of/container_of.h>
54

55
static PyObject *gcmodule;
56
static PyObject *pybuf_str;
57

58 59
/* whether to pass old buffer instead of memoryview to .loadblk() / .storeblk()
 *
Kirill Smelkov's avatar
Kirill Smelkov committed
60
 * on python2 < 2.7.10 memoryview object is not accepted in a lot of
61 62
 * places, see e.g. http://bugs.python.org/issue22113 for struct.pack_into()
 *
63 64 65
 * also on python 2.7.10, even latest numpy does not accept memoryview as
 * buffer for ndarray: https://github.com/numpy/numpy/issues/5935
 *
66 67 68
 * if we know memoryview won't be accepted - we pass old buffer
 *
 * TODO get rid of this and eventually use only memoryview  */
69 70 71
//#define BIGFILE_USE_OLD_BUFFER  (PY_VERSION_HEX < 0x02070AF0)
// waiting for numpy to start accept it on python2
#define BIGFILE_USE_OLD_BUFFER  (PY_VERSION_HEX < 0x03000000)
72

73 74 75 76 77 78 79
/* whether to use PyThreadState->exc_state,exc_info instead of
 * PyThreadState->exc_type & friends.
 *
 * Starting from Python 3.7 the place to keep exception state was changed:
 * https://github.com/python/cpython/commit/ae3087c638  */
#define BIGFILE_USE_PYTS_EXC_INFO   (PY_VERSION_HEX >= 0x030700A3)

80 81 82 83 84

/* like PyObject_New, but fully initializes instance (e.g. calls type ->tp_new) */
#define PyType_New(type, typeobj, args) \
    ( (type *)PyObject_CallObject((PyObject *)(typeobj), args) )

85 86 87
/* like PyErr_SetFromErrno(exc), but chooses exception type automatically */
static void XPyErr_SetFromErrno(void);

88 89 90 91
/* like PyErr_Clear but clears not only ->curexc_* but also ->exc_* and
 * everything else related to exception state */
static void XPyErr_FullClear(void);

92 93 94
/* get list of objects that refer to obj */
static PyObject* /* PyListObject* */ XPyObject_GetReferrers(PyObject *obj);

95
/* print objects that refer to obj */
96
void XPyObject_PrintReferrers(PyObject *obj, FILE *fp);
97

98 99 100
/* check whether frame f is a callee of top */
static int XPyFrame_IsCalleeOf(PyFrameObject *f, PyFrameObject *top);

101
/* buffer utilities: unpin buffer from its memory - make it zero-length
Kirill Smelkov's avatar
Kirill Smelkov committed
102
 * pointing to NULL but staying a valid python object */
103 104 105 106 107
#if PY_MAJOR_VERSION < 3
void XPyBufferObject_Unpin(PyBufferObject *bufo);
#endif
void XPyBuffer_Unpin(Py_buffer *view);

108

109 110 111 112 113
#define PyFunc(FUNC, DOC)               \
static const char FUNC ##_doc[] = DOC;  \
static PyObject *FUNC


114 115 116 117 118 119 120 121
/************
 *  PyVMA   *
 ************/

#if PY_MAJOR_VERSION < 3
static Py_ssize_t
pyvma_getbuf(PyObject *pyvma0, Py_ssize_t segment, void **pptr)
{
122 123
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);
    VMA   *vma   = &pyvma->vma;
124 125 126 127 128 129

    if (segment) {
        PyErr_SetString(PyExc_SystemError, "access to non-zero vma segment");
        return -1;
    }

130 131
    *pptr = (void *)vma->addr_start;
    return vma->addr_stop - vma->addr_start;
132 133 134 135 136 137
}


static Py_ssize_t
pyvma_getsegcount(PyObject *pyvma0, Py_ssize_t *lenp)
{
138 139
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);
    VMA   *vma   = &pyvma->vma;
140 141

    if (lenp)
142
        *lenp = vma->addr_stop - vma->addr_start;
143 144 145 146 147 148 149 150
    return 1;
}
#endif


static int
pyvma_getbuffer(PyObject *pyvma0, Py_buffer *view, int flags)
{
151 152
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);
    VMA   *vma   = &pyvma->vma;
153

154 155
    return PyBuffer_FillInfo(view, &pyvma->pyobj,
            (void *)vma->addr_start, vma->addr_stop - vma->addr_start,
156 157 158 159 160 161 162 163
            /*readonly=*/0, flags);
}


/* vma len in bytes */
static Py_ssize_t
pyvma_len(PyObject *pyvma0)
{
164 165
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);
    VMA   *vma   = &pyvma->vma;
166

167
    return vma->addr_stop - vma->addr_start;
168 169 170
}


171 172 173
PyFunc(pyvma_filerange, "filerange() -> (pgoffset, pglen) -- file range this vma covers")
    (PyObject *pyvma0, PyObject *args)
{
174 175
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);
    VMA   *vma   = &pyvma->vma;
176 177
    Py_ssize_t pgoffset, pglen;     // XXX Py_ssize_t vs pgoff_t

178 179
    pgoffset = vma->f_pgoffset;
    pglen    = (vma->addr_stop - vma->addr_start) / vma->fileh->ramh->ram->pagesize;
180 181 182 183 184 185 186 187 188
    /* NOTE ^^^ addr_stop and addr_start must be page-aligned */

    return Py_BuildValue("(nn)", pgoffset, pglen);
}


PyFunc(pyvma_pagesize, "pagesize() -> pagesize -- page size of RAM underlying this VMA")
    (PyObject *pyvma0, PyObject *args)
{
189 190 191
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);
    VMA   *vma   = &pyvma->vma;
    Py_ssize_t pagesize = vma->fileh->ramh->ram->pagesize;
192 193 194 195 196

    return Py_BuildValue("n", pagesize);
}


197 198 199 200 201 202
/* pyvma vs cyclic GC */
static int
pyvma_traverse(PyObject *pyvma0, visitproc visit, void *arg)
{
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);

203 204
    /* NOTE don't traverse vma->fileh (see pyvma_clear for details) */

205 206 207 208 209 210 211 212 213
    Py_VISIT(pyvma->pyuser);
    return 0;
}

static int
pyvma_clear(PyObject *pyvma0)
{
    PyVMA *pyvma = container_of(pyvma0, PyVMA, pyobj);

214 215 216 217 218 219
    /* NOTE don't clear vma->fileh: we need vma to be released first - else -
     * if there would be vma <=> fileh cycle, it could be possible for Python
     * to release fileh first, and then fileh_close called by fileh release
     * would break with BUG asserting that there are no fileh mappings left.
     * Protect py-level users from that. */

220 221 222 223 224
    Py_CLEAR(pyvma->pyuser);
    return 0;
}


225 226 227
static void
pyvma_dealloc(PyObject *pyvma0)
{
228 229 230 231 232 233 234
    /* PyVMA supports cyclic GC - first, before destructing, remove it from GC
     * list to avoid segfaulting on double entry here - e.g. if GC is triggered
     * from a weakref callback, or concurrently from another thread.
     *
     * See https://bugs.python.org/issue31095 for details */
    PyObject_GC_UnTrack(pyvma0);

235 236 237
    PyVMA       *pyvma = container_of(pyvma0, PyVMA, pyobj);
    VMA         *vma   = &pyvma->vma;
    BigFileH    *fileh = vma->fileh;
238 239

    if (pyvma->in_weakreflist)
240
        PyObject_ClearWeakRefs(&pyvma->pyobj);
241

242
    /* pyvma->fileh indicates whether vma was yet created (via fileh_mmap()) or not */
243
    if (fileh) {
244
        vma_unmap(vma);
245

246
        PyBigFileH *pyfileh = container_of(fileh, PyBigFileH, fileh);
247 248 249
        Py_DECREF(pyfileh);
    }

250 251
    pyvma_clear(&pyvma->pyobj);
    pyvma->pyobj.ob_type->tp_free(&pyvma->pyobj);
252 253 254 255 256 257 258 259 260 261 262 263 264
}


static PyObject *
pyvma_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
    PyVMA *self;

    self = (PyVMA *)PyType_GenericNew(type, args, kw);
    if (!self)
        return NULL;
    self->in_weakreflist = NULL;

265
    return &self->pyobj;
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
}


static /*const*/ PyBufferProcs pyvma_as_buffer = {
#if PY_MAJOR_VERSION < 3
    /* for buffer() */
    .bf_getreadbuffer   = pyvma_getbuf,
    .bf_getwritebuffer  = pyvma_getbuf,
    .bf_getsegcount     = pyvma_getsegcount,
#endif

    /* for memoryview()
     * NOTE but ndarray() ctor does not get memoryview as buffer */ // XXX recheck
    .bf_getbuffer       = pyvma_getbuffer,
};


static /*const*/ PySequenceMethods pyvma_as_seq = {
    .sq_length          = pyvma_len,
};


288 289 290 291 292 293
static /*const*/ PyMethodDef pyvma_methods[] = {
    {"filerange",   pyvma_filerange,    METH_VARARGS,   pyvma_filerange_doc},
    {"pagesize",    pyvma_pagesize,     METH_VARARGS,   pyvma_pagesize_doc},
    {NULL}
};

Kirill Smelkov's avatar
Kirill Smelkov committed
294
// XXX vvv better switch on various possibilities and find appropriate type
295 296 297 298 299 300
// (e.g. on X32 uintptr_t will be 4 while long will be 8)
const int _ =
    BUILD_ASSERT_OR_ZERO(sizeof(uintptr_t) == sizeof(unsigned long));
#define T_UINTPTR   T_ULONG

static /*const*/ PyMemberDef pyvma_members[] = {
301 302
    {"addr_start",  T_UINTPTR,      offsetof(PyVMA, vma.addr_start),    READONLY, "vma's start addr"},
    {"addr_stop",   T_UINTPTR,      offsetof(PyVMA, vma.addr_stop),     READONLY, "vma's start addr"},
303
    // XXX pyuser: restrict to read-only access?
304
    {"pyuser",      T_OBJECT_EX,    offsetof(PyVMA, pyuser),            0,        "user of this vma"},
305 306 307
    {NULL}
};

308 309 310 311
static PyTypeObject PyVMA_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = "_bigfile.VMA",
    .tp_basicsize       = sizeof(PyVMA),
312 313 314 315 316
    .tp_flags           = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_NEWBUFFER | Py_TPFLAGS_HAVE_GC,
    .tp_traverse        = pyvma_traverse,
    .tp_clear           = pyvma_clear,
    .tp_methods         = pyvma_methods,
    .tp_members         = pyvma_members,
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
    .tp_as_sequence     = &pyvma_as_seq,
    .tp_as_buffer       = &pyvma_as_buffer,
    .tp_dealloc         = pyvma_dealloc,
    .tp_new             = pyvma_new,
    .tp_weaklistoffset  = offsetof(PyVMA, in_weakreflist),
    .tp_doc             = "memory area representing one fileh mapping"
};



/****************
 *  PyBigFileH  *
 ****************/


PyFunc(pyfileh_mmap, "mmap(pgoffset, pglen) - map fileh part into memory")
    (PyObject *pyfileh0, PyObject *args)
{
335
    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
336 337 338 339 340 341 342 343 344 345 346 347
    Py_ssize_t  pgoffset, pglen;    // XXX Py_ssize_t vs pgoff_t ?
    PyVMA       *pyvma;
    int         err;

    if (!PyArg_ParseTuple(args, "nn", &pgoffset, &pglen))
        return NULL;

    pyvma = PyType_New(PyVMA, &PyVMA_Type, NULL);
    if (!pyvma)
        return NULL;

    Py_INCREF(pyfileh);
348
    err = fileh_mmap(&pyvma->vma, &pyfileh->fileh, pgoffset, pglen);
349 350
    if (err) {
        Py_DECREF(pyfileh);
351
        Py_DECREF(pyvma);
352
        XPyErr_SetFromErrno();
353 354 355
        return NULL;
    }

356
    return &pyvma->pyobj;
357 358 359 360 361 362 363
}


PyFunc(pyfileh_dirty_writeout,
        "dirty_writeout(flags) - write changes made to fileh memory back to file")
    (PyObject *pyfileh0, PyObject *args)
{
364 365
    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
    BigFileH    *fileh   = &pyfileh->fileh;
366 367 368 369 370 371
    long flags;
    int err;

    if (!PyArg_ParseTuple(args, "l", &flags))
        return NULL;

372
    err = fileh_dirty_writeout(fileh, flags);
373
    if (err) {
374 375 376
        if (!PyErr_Occurred())
            // XXX not very informative
            PyErr_SetString(PyExc_RuntimeError, "fileh_dirty_writeout fail");
377 378 379 380 381 382 383 384 385 386
        return NULL;
    }

    Py_RETURN_NONE;
}


PyFunc(pyfileh_dirty_discard, "dirty_discard() - discard changes made to fileh memory")
    (PyObject *pyfileh0, PyObject *args)
{
387 388
    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
    BigFileH    *fileh   = &pyfileh->fileh;
389 390 391 392

    if (!PyArg_ParseTuple(args, ""))
        return NULL;

393
    fileh_dirty_discard(fileh);
394 395 396 397 398 399 400
    Py_RETURN_NONE;
}


PyFunc(pyfileh_isdirty, "isdirty() - are there any changes to fileh memory at all?")
    (PyObject *pyfileh0, PyObject *args)
{
401 402
    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
    BigFileH    *fileh   = &pyfileh->fileh;
403 404 405 406

    if (!PyArg_ParseTuple(args, ""))
        return NULL;

Kirill Smelkov's avatar
Kirill Smelkov committed
407
    /* NOTE not strictly necessary to virt_lock() for checking ->dirty_pages not empty */
408
    return PyBool_FromLong(!list_empty(&fileh->dirty_pages));
409 410 411
}


412 413 414
PyFunc(pyfileh_invalidate_page, "invalidate_page(pgoffset) - invalidate fileh page")
    (PyObject *pyfileh0, PyObject *args)
{
415 416
    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
    BigFileH    *fileh   = &pyfileh->fileh;
417 418 419 420 421
    Py_ssize_t  pgoffset;   // XXX Py_ssize_t vs pgoff_t ?

    if (!PyArg_ParseTuple(args, "n", &pgoffset))
        return NULL;

422
    fileh_invalidate_page(fileh, pgoffset);
423 424 425 426 427

    Py_RETURN_NONE;
}


428

429 430 431 432
/* pyfileh vs cyclic GC */
static int
pyfileh_traverse(PyObject *pyfileh0, visitproc visit, void *arg)
{
433 434 435
    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
    BigFileH    *fileh   = &pyfileh->fileh;
    BigFile     *file    = fileh->file;
436 437
    PyBigFile   *pyfile;

438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
    if (file) {
        pyfile = container_of(file, PyBigFile, file);
        Py_VISIT(pyfile);
    }

    return 0;
}

static int
pyfileh_clear(PyObject *pyfileh0)
{
    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
    BigFileH    *fileh   = &pyfileh->fileh;
    BigFile     *file    = fileh->file;
    PyBigFile   *pyfile;
453

454
    /* pyfileh->file indicates whether fileh was yet opened (via fileh_open()) or not */
455
    if (file) {
456 457 458 459 460
        pyfile = container_of(file, PyBigFile, file);

        /* NOTE calling fileh_close in tp_clear - it is a bit hacky but ok.
         * we have to call fileh_close now becuase we'll reset fileh->file=NULL next.
         * pyfileh_dealloc also calls pyfileh_clear. */
461
        fileh_close(fileh);
462

463 464 465
        Py_DECREF(pyfile);
    }

466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    return 0;
}


static void
pyfileh_dealloc(PyObject *pyfileh0)
{
    /* PyBigFileH supports cyclic GC - first, before destructing, remove it from GC
     * list to avoid segfaulting on double entry here - e.g. if GC is triggered
     * from a weakref callback, or concurrently from another thread.
     *
     * See https://bugs.python.org/issue31095 for details */
    PyObject_GC_UnTrack(pyfileh0);

    PyBigFileH  *pyfileh = container_of(pyfileh0, PyBigFileH, pyobj);
    BigFileH    *fileh   = &pyfileh->fileh;
    BigFile     *file    = fileh->file;
    PyBigFile   *pyfile;

    if (pyfileh->in_weakreflist)
        PyObject_ClearWeakRefs(&pyfileh->pyobj);

    pyfileh_clear(&pyfileh->pyobj);
489
    pyfileh->pyobj.ob_type->tp_free(&pyfileh->pyobj);
490 491 492 493 494 495 496 497 498 499 500 501 502
}


static PyObject *
pyfileh_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
    PyBigFileH *self;

    self = (PyBigFileH *)PyType_GenericNew(type, args, kw);
    if (!self)
        return NULL;
    self->in_weakreflist = NULL;

503
    return &self->pyobj;
504 505 506 507 508 509 510 511
}


static /*const*/ PyMethodDef pyfileh_methods[] = {
    {"mmap",            pyfileh_mmap,           METH_VARARGS,   pyfileh_mmap_doc},
    {"dirty_writeout",  pyfileh_dirty_writeout, METH_VARARGS,   pyfileh_dirty_writeout_doc},
    {"dirty_discard",   pyfileh_dirty_discard,  METH_VARARGS,   pyfileh_dirty_discard_doc},
    {"isdirty",         pyfileh_isdirty,        METH_VARARGS,   pyfileh_isdirty_doc},
512
    {"invalidate_page", pyfileh_invalidate_page,METH_VARARGS,   pyfileh_invalidate_page_doc},
513 514 515 516 517 518 519 520
    {NULL}
};


static /*const*/ PyTypeObject PyBigFileH_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = "_bigfile.BigFileH",
    .tp_basicsize       = sizeof(PyBigFileH),
521 522 523
    .tp_flags           = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
    .tp_traverse        = pyfileh_traverse,
    .tp_clear           = pyfileh_clear,
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
    .tp_methods         = pyfileh_methods,
    .tp_dealloc         = pyfileh_dealloc,
    .tp_new             = pyfileh_new,
    .tp_weaklistoffset  = offsetof(PyBigFileH, in_weakreflist),
    .tp_doc             = "BigFile Handle"
};




/****************
 *  PyBigFile   *
 ****************/


static int pybigfile_loadblk(BigFile *file, blk_t blk, void *buf)
{
541
    PyBigFile *pyfile = container_of(file, PyBigFile, file);
542 543 544 545 546 547
    PyObject  *pybuf = NULL;
    PyObject  *loadret = NULL;

    PyGILState_STATE gstate;
    PyThreadState *ts;
    PyFrameObject *ts_frame_orig;
548
    PyObject  *exc_type, *exc_value, *exc_traceback;
549 550
    PyObject  *save_curexc_type, *save_curexc_value, *save_curexc_traceback;
    PyObject  *save_exc_type,    *save_exc_value,    *save_exc_traceback;
551 552 553
#if BIGFILE_USE_PYTS_EXC_INFO
    _PyErr_StackItem *save_exc_info;
#endif
554 555 556 557 558 559 560 561 562
    PyObject  *save_async_exc;
    // XXX save/restore trash_delete_{nesting,later} ?


    /* ensure we hold the GIL
     * if not already taken - we'll acquire it; if already hold - no action.
     * as the result - _we_ are the thread which holds the GIL and can call
     * python capi. */
    // XXX assert PyGILState_GetThisThreadState() != NULL
Kirill Smelkov's avatar
Kirill Smelkov committed
563
    //      (i.e. python already knows this thread?)
564 565
    gstate = PyGILState_Ensure();

Kirill Smelkov's avatar
Kirill Smelkov committed
566
    /* TODO write why we setup completely new thread state which looks like
567 568 569
     * switching threads for python but stays at the same OS thread
     *
     * a) do not change current thread state in any way;
Kirill Smelkov's avatar
Kirill Smelkov committed
570
     * b) to completely clear ts after loadblk (ex. for pybuf->refcnf to go to exactly 1)
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
     */

    /* in python thread state - save what we'll possibly override
     *
     * to faulting code, it looks like loadblk() is just called as callback.
     * Only we have to care to restore exception states for caller (we can't
     * propagate exceptions through pagefaulting, and if there were series of
     * try/except, we too should restore original caller exception states.
     *
     * TODO better text.
     */
    ts = PyThreadState_GET();
    ts_frame_orig = ts->frame;  // just for checking

/* set ptr to NULL and return it's previous value */
#define set0(pptr)  ({ typeof(*(pptr)) p = *(pptr); *(pptr)=NULL; p; })

/* xincref that evaluate its argument only once */
#define XINC(arg)   do { typeof(arg) p = (arg); Py_XINCREF(p); } while (0)

/* like set0 for PySys_{Get,Set}Object, but also xincref original obj */
#define PySys_XInc_SetNone(name)    ({                                  \
    PyObject *p = PySys_GetObject(name);                                \
    /* incref here because otherwise on next line it can go away */     \
    Py_XINCREF(p);                                                      \
    PySys_SetObject(name, Py_None);                                     \
    p;                                                                  \
})

    XINC( save_curexc_type        = set0(&ts->curexc_type)        );
    XINC( save_curexc_value       = set0(&ts->curexc_value)       );
    XINC( save_curexc_traceback   = set0(&ts->curexc_traceback)   );
603 604 605 606 607 608 609 610 611

#if BIGFILE_USE_PYTS_EXC_INFO
    XINC( save_exc_type           = set0(&ts->exc_state.exc_type)       );
    XINC( save_exc_value          = set0(&ts->exc_state.exc_value)      );
    XINC( save_exc_traceback      = set0(&ts->exc_state.exc_traceback)  );
    save_exc_info = ts->exc_info;
    ts->exc_info = &ts->exc_state;
    BUG_ON(ts->exc_state.previous_item != NULL);
#else
612 613 614
    XINC( save_exc_type           = set0(&ts->exc_type)           );
    XINC( save_exc_value          = set0(&ts->exc_value)          );
    XINC( save_exc_traceback      = set0(&ts->exc_traceback)      );
615 616
#endif

617 618 619
    XINC( save_async_exc          = set0(&ts->async_exc)          );

    /* before py3k python also stores exception in sys.exc_* variables (wrt
620 621 622
     * sys.exc_info()) for "backward compatibility", but we do not care about it
     * as sys.exc_* variables are not thread safe and from a thread point of view
     * can be changing at arbitrary times during while its python code runs. */
623 624 625 626 627 628 629 630 631 632 633

#if BIGFILE_USE_OLD_BUFFER
    pybuf = PyBuffer_FromReadWriteMemory(buf, file->blksize);
#else
    pybuf = PyMemoryView_FromMemory(buf, file->blksize, PyBUF_WRITE /* = read-write */);
#endif
    if (!pybuf)
        goto err;

    /* NOTE K = unsigned long long */
    BUILD_ASSERT(sizeof(blk) == sizeof(unsigned long long));
634
    loadret = PyObject_CallMethod(&pyfile->pyobj, "loadblk", "KO", blk, pybuf);
635 636

    /* python should return to original frame */
637
    BUG_ON(ts != PyThreadState_GET());
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
    BUG_ON(ts->frame != ts_frame_orig);

    if (!loadret)
        goto err;

out:
    /* clear thread state to cleanup, and in particular so it does not hold
     * reference to pybuf - after return from loadblk, buf memory will be
     * unmapped, so the caller must not have left kept references to it. */

    /* we need to know only whether loadret != NULL, decref it now */
    Py_XDECREF(loadret);


    /* first clear exception states so it drop all references (and possibly in
653 654
     * called frame) to pybuf */
    XPyErr_FullClear();
655 656

    /* verify pybuf is not held - its memory will go away right after return */
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    if (pybuf) {
        /* pybuf might be held e.g. due to an exception raised & caught
         * somewhere in loadblk implementation - so loadblk returns ok, but if
         *
         *   _, _, exc_traceback = sys.exc_info()
         *
         * was also used inside the following reference loop is created:
         *
         *   exc_traceback
         *     |        ^
         *     |        |
         *     v     .f_localsplus
         *    frame
         *
         * and some of the frames continue to hold pybuf reference.
         *
673 674
         * Do full GC to collect such, and possibly other, cycles this way
         * removing references to pybuf.
675 676
         */
        if (pybuf->ob_refcnt != 1) {
677 678 679 680 681 682
            /* NOTE this could be noop if GC was already started from another
             * thread, called some python code via decref and then while python
             * code there was executing - python thread switch happens to us to
             * come here with gc.collecting=1
             *
             * NOTE also: while collecting garbage even more garbage can be
Kirill Smelkov's avatar
Kirill Smelkov committed
683
             * created due to arbitrary code run from under __del__ of released
684 685 686
             * objects and weakref callbacks. This way after here GC collect
             * even a single allocation could trigger GC, and thus arbitrary
             * python code run, again */
687 688
            PyGC_Collect();

Kirill Smelkov's avatar
Kirill Smelkov committed
689
            /* garbage collection could result in running arbitrary code
690 691 692 693 694 695 696
             * because of finalizers. Print problems (if any) and make sure
             * once again exception state is clear */
            if (PyErr_Occurred())
                PyErr_PrintEx(0);
            XPyErr_FullClear();
        }

697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
        /* the story continues here - a traceback might be also explicitly
         * saved by code somewhere this way not going away after GC. Let's
         * find objects that refer to pybuf, and for frames called by loadblk()
         * change pybuf to another stub object (we know there we can do it safely) */
        if (pybuf->ob_refcnt != 1) {
            PyObject *pybuf_users = XPyObject_GetReferrers(pybuf);
            int i, j;

            for (i = 0; i < PyList_GET_SIZE(pybuf_users); i++) {
                PyObject *user = PyList_GET_ITEM(pybuf_users, i);
                PyObject **fastlocals;
                PyFrameObject *f;

                /* if it was the frame used for our calling to py loadblk() we
                 * can replace pybuf to "<pybuf>" there in loadblk arguments */
                if (PyFrame_Check(user)) {
                    f = (PyFrameObject *)user;
                    if (!XPyFrame_IsCalleeOf(f, ts->frame))
                        continue;

                    /* "fast" locals (->f_localsplus) */
                    fastlocals = f->f_localsplus;
                    for (j = f->f_code->co_nlocals; j >= 0; --j) {
                        if (fastlocals[j] == pybuf) {
                            Py_INCREF(pybuf_str);
                            fastlocals[j] = pybuf_str;
                            Py_DECREF(pybuf);
                        }
                    }

                    /* ->f_locals */
                    if (f->f_locals != NULL) {
                        TODO(!PyDict_CheckExact(f->f_locals));

                        PyObject *key, *value;
                        Py_ssize_t pos = 0;

                        while (PyDict_Next(f->f_locals, &pos, &key, &value)) {
                            if (value == pybuf) {
                                int err;
                                err = PyDict_SetItem(f->f_locals, key, pybuf_str);
                                BUG_ON(err == -1);
                            }
                        }
                    }
                }
            }

            Py_DECREF(pybuf_users);
746 747 748 749 750 751 752 753

            /* see note ^^^ around PyGC_Collect() call that we can land here
             * with arbitrary python code ran again (because e.g.
             * XPyObject_GetReferrers() allocates at least a list and that
             * might trigger automatic GC again */
            if (PyErr_Occurred())
                PyErr_PrintEx(0);
            XPyErr_FullClear();
754 755
        }

756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
        /* above attempts were "best effort" to unreference pybuf. However we
         * cannot completely do so. For example if
         *  1. f_locals dict reference pybuf
         *  2. f_locals contains only not-tracked by GC keys & values (and pybuf is not tracked)
         *  3. frame object for which f_locals was created is already garbage-collected
         *
         * the f_locals dict won't be even listed in pybuf referrers (python
         * dicts and tuples with all atomic types becomes not tracked by GC),
         * so we cannot even inspect it.
         *
         * if nothing helped, as a last resort, unpin pybuf from its original
         * memory and make it point to zero-sized NULL.
         *
         * In general this is not strictly correct to do as other buffers &
         * memoryview objects created from pybuf, copy its pointer on
         * initialization and thus pybuf unpinning won't adjust them.
         *
         * However we require BigFile implementations to make sure not to use
         * such-created objects, if any, after return from loadblk().
         */
        if (pybuf->ob_refcnt != 1) {
#if BIGFILE_USE_OLD_BUFFER
            PyBufferObject *pybufo = (PyBufferObject *)pybuf;
            XPyBufferObject_Unpin(pybufo);
#else
            PyMemoryViewObject *pybufm = (PyMemoryViewObject *)pybuf;
            XPyBuffer_Unpin(&pybufm->view);
#endif
784 785 786 787 788 789

            /* we released pybuf->base object which might run some code in __del__
             * clear exception state again */
            if (PyErr_Occurred())
                PyErr_PrintEx(0);
            XPyErr_FullClear();
790 791 792
        }

#if 0
793
        /* now it is real bug if pybuf remains referenced from somewhere */
794 795 796 797 798 799 800 801 802
        if (pybuf->ob_refcnt != 1) {
            WARN("pybuf->ob_refcnt != 1 even after GC:");
            fprintf(stderr, "pybuf (ob_refcnt=%ld):\t", (long)pybuf->ob_refcnt);
            PyObject_Print(pybuf, stderr, 0);
            fprintf(stderr, "\npybuf referrers:\t");
            XPyObject_PrintReferrers(pybuf, stderr);
            fprintf(stderr, "\n");
            BUG();
        }
803
#endif
804
    }
805 806 807 808 809 810

    /* drop pybuf
     *
     * NOTE in theory decref could run arbitrary code, but buffer_dealloc() is
     * simply PyObject_DEL which does not lead to running python code. */
    Py_XDECREF(pybuf);
811 812 813 814 815
    if (PyErr_Occurred()) {
        WARN("python thread-state found with exception set; but should not");
        WARN("I will dump the exception and then crash");
        PyErr_PrintEx(0);
    }
816
    BUG_ON(ts->curexc_type  || ts->curexc_value || ts->curexc_traceback);
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
#if BIGFILE_USE_PYTS_EXC_INFO
    if (ts->exc_info != &ts->exc_state) {
        WARN("python thread-state found with active generator after loadblk call");
        WARN("I will crash"); // TODO dump which generator is still running
        BUG();
    }
    exc_type        = ts->exc_state.exc_type;
    exc_value       = ts->exc_state.exc_value;
    exc_traceback   = ts->exc_state.exc_traceback;
#else
    exc_type        = ts->exc_type;
    exc_value       = ts->exc_value;
    exc_traceback   = ts->exc_traceback;
#endif
    if (exc_type) {
832 833
        WARN("python thread-state found with handled but not cleared exception state");
        WARN("I will dump it and then crash");
834 835 836
        fprintf(stderr, "ts->exc_type:\t");         PyObject_Print(exc_type, stderr, 0);
        fprintf(stderr, "\nts->exc_value:\t");      PyObject_Print(exc_value, stderr, 0);
        fprintf(stderr, "\nts->exc_traceback:\t");  PyObject_Print(exc_traceback, stderr, 0);
837
        fprintf(stderr, "\n");
838
        PyErr_Display(exc_type, exc_value, exc_traceback);
839
    }
840
    BUG_ON(exc_type     || exc_value    || exc_traceback);
841 842 843 844 845 846
    BUG_ON(ts->async_exc);

    /* now restore exception state to original */
    ts->curexc_type         = save_curexc_type;
    ts->curexc_value        = save_curexc_value;
    ts->curexc_traceback    = save_curexc_traceback;
847 848 849 850 851 852 853 854

#if BIGFILE_USE_PYTS_EXC_INFO
    ts->exc_state.exc_type      = save_exc_type;
    ts->exc_state.exc_value     = save_exc_value;
    ts->exc_state.exc_traceback = save_exc_traceback;
    ts->exc_info = save_exc_info;
    BUG_ON(ts->exc_state.previous_item != NULL);
#else
855 856 857
    ts->exc_type            = save_exc_type;
    ts->exc_value           = save_exc_value;
    ts->exc_traceback       = save_exc_traceback;
858 859
#endif

860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
    ts->async_exc           = save_async_exc;

    Py_XDECREF( save_curexc_type        );
    Py_XDECREF( save_curexc_value       );
    Py_XDECREF( save_curexc_traceback   );
    Py_XDECREF( save_exc_type           );
    Py_XDECREF( save_exc_value          );
    Py_XDECREF( save_exc_traceback      );
    Py_XDECREF( save_async_exc          );

    /* optionally release the GIL, if it was not ours initially */
    PyGILState_Release(gstate);

    /* loadblk() job done */
    return loadret ? 0 : -1;

err:
    /* error happened - dump traceback and return */
    PyErr_PrintEx(0);
    goto out;
}

#undef XINC


static int pybigfile_storeblk(BigFile *file, blk_t blk, const void *buf)
{
887
    PyBigFile *pyfile = container_of(file, PyBigFile, file);
888
    PyObject  *pybuf;
889 890 891
    PyObject  *storeret = NULL;

    PyGILState_STATE gstate;
892
    int err;
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907

    // XXX so far storeblk() is called only from py/user context (vs SIGSEGV
    //     context) - no need to save/restore interpreter state.
    // TODO -> if needed - do similar to pybigfile_loadblk().

    /* ensure we hold the GIL (and thus are _the_ python thread) */
    gstate = PyGILState_Ensure();

    // XXX readonly, but wants (void *) without const
#if BIGFILE_USE_OLD_BUFFER
    pybuf = PyBuffer_FromMemory((void *)buf, file->blksize);
#else
    pybuf = PyMemoryView_FromMemory((void *)buf, file->blksize, PyBUF_READ);
#endif
    if (!pybuf)
908
        goto out;
909 910 911

    /* NOTE K = unsigned long long */
    BUILD_ASSERT(sizeof(blk) == sizeof(unsigned long long));
912
    storeret = PyObject_CallMethod(&pyfile->pyobj, "storeblk", "KO", blk, pybuf);
913 914 915 916

    /* we need to know only whether storeret != NULL, decref it now */
    Py_XDECREF(storeret);

917 918 919 920 921
    /* FIXME the following is not strictly correct e.g. for:
     *   mbuf = memoryview(buf)
     * because mbuf.ptr will be a copy of buf.ptr and clearing buf does not
     * clear mbuf.
     *
922 923 924 925
     * However we require BigFile implementations to make sure not to use
     * such-created objects, if any, after return from storeblk().
     *
     * See more details in loadblk() codepath */
926

927 928 929 930 931 932
    /* repoint pybuf to empty region - the original memory attached to it can
     * go away right after we return (if e.g. dirty page was not mapped in any
     * vma), but we need pybuf to stay not corrupt - for printing full
     * traceback in case of storeblk() error. */
#if BIGFILE_USE_OLD_BUFFER
    PyBufferObject *pybufo = (PyBufferObject *)pybuf;
933
    XPyBufferObject_Unpin(pybufo);
934 935
#else
    PyMemoryViewObject *pybufm = (PyMemoryViewObject *)pybuf;
936
    XPyBuffer_Unpin(&pybufm->view);
937
#endif
938

939 940 941 942 943 944 945 946 947 948
    /* verify that we actually tweaked pybuf ok */
    Py_buffer pybuf_view;
    err = PyObject_GetBuffer(pybuf, &pybuf_view, PyBUF_SIMPLE);
    BUG_ON(err);
    BUG_ON(pybuf_view.buf   != NULL);
    BUG_ON(pybuf_view.len   != 0);
    PyBuffer_Release(&pybuf_view);

    /* done with pybuf */
    Py_DECREF(pybuf);
949

950
out:
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968
    PyGILState_Release(gstate);

    /* storeblk() job done */
    return storeret ? 0 : -1;
}


static const struct bigfile_ops pybigfile_ops = {
    .loadblk    = pybigfile_loadblk,
    .storeblk   = pybigfile_storeblk,
    //.release    =
};



static PyObject *
pyfileh_open(PyObject *pyfile0, PyObject *args)
{
969
    PyBigFile   *pyfile = container_of(pyfile0, PyBigFile, pyobj);
970
    PyBigFileH  *pyfileh;
971
    /* NOTE no virtmem lock needed - default RAM does not change */
972 973 974 975 976 977 978 979 980 981 982 983
    RAM *ram = ram_get_default(NULL);   // TODO get ram from args
    int err;


    if (!PyArg_ParseTuple(args, ""))
        return NULL;

    pyfileh = PyType_New(PyBigFileH, &PyBigFileH_Type, NULL);
    if (!pyfileh)
        return NULL;

    Py_INCREF(pyfile);
984
    err = fileh_open(&pyfileh->fileh, &pyfile->file, ram);
985
    if (err) {
986
        XPyErr_SetFromErrno();
987 988 989 990 991
        Py_DECREF(pyfile);
        Py_DECREF(pyfileh);
        return NULL;
    }

992
    return &pyfileh->pyobj;
993 994
}

995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
/* pyfile vs cyclic GC */
static int
pyfile_traverse(PyObject *pyfile0, visitproc visit, void *arg)
{
    PyBigFile  *pyfile = container_of(pyfile0, PyBigFile, pyobj);

    return 0;
}

static int
pyfile_clear(PyObject *pyfile0)
{
    PyBigFile  *pyfile = container_of(pyfile0, PyBigFile, pyobj);

    return 0;
}


static void
pyfile_dealloc(PyObject *pyfile0)
{
    /* PyBigFile supports cyclic GC - first, before destructing, remove it from GC
     * list to avoid segfaulting on double entry here - e.g. if GC is triggered
     * from a weakref callback, or concurrently from another thread.
     *
     * See https://bugs.python.org/issue31095 for details */
    PyObject_GC_UnTrack(pyfile0);

    PyBigFile  *pyfile = container_of(pyfile0, PyBigFile, pyobj);

    pyfile_clear(&pyfile->pyobj);
    pyfile->pyobj.ob_type->tp_free(&pyfile->pyobj);
}

1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040

static PyObject *
pyfile_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
    PyBigFile *self;

    self = (PyBigFile *)PyType_GenericNew(type, args, kw);
    if (!self)
        return NULL;

    // FIXME "k" = unsigned long - we need size_t
    static char *kw_list[] = {"blksize", NULL};
1041
    if (!PyArg_ParseTupleAndKeywords(args, kw, "k", kw_list, &self->file.blksize)) {
1042 1043 1044 1045
        Py_DECREF(self);
        return NULL;
    }

1046 1047
    self->file.file_ops = &pybigfile_ops;
    return &self->pyobj;
1048 1049 1050 1051
}


static PyMemberDef pyfile_members[] = {
1052
    {"blksize", T_ULONG /* XXX vs typeof(blksize) = size_t ? */, offsetof(PyBigFile, file.blksize), READONLY, "block size"},
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
    {NULL}
};

static /*const*/ PyMethodDef pyfile_methods[] = {
    {"fileh_open",  pyfileh_open,   METH_VARARGS,   "fileh_open(ram=None) -> new file handle"},
    {NULL}
};

static PyTypeObject PyBigFile_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name            = "_bigfile.BigFile",
    .tp_basicsize       = sizeof(PyBigFile),
1065 1066 1067
    .tp_flags           = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
    .tp_traverse        = pyfile_traverse,
    .tp_clear           = pyfile_clear,
1068 1069
    .tp_methods         = pyfile_methods,
    .tp_members         = pyfile_members,
1070
    .tp_dealloc         = pyfile_dealloc,
1071 1072 1073 1074 1075 1076 1077 1078 1079
    .tp_new             = pyfile_new,
    .tp_doc             = "Base class for creating BigFile(s)\n\nTODO describe",    // XXX
};



PyFunc(pyram_reclaim, "ram_reclaim() -> reclaimed -- release some non-dirty ram back to OS")
    (PyObject *self, PyObject *args)
{
1080
    /* NOTE no virtmem lock needed - default RAM does not change */
1081 1082 1083 1084 1085 1086 1087 1088 1089
    RAM *ram = ram_get_default(NULL);   // TODO get ram from args
    int reclaimed;

    if (!PyArg_ParseTuple(args, ""))
        return NULL;

    reclaimed = ram_reclaim(ram);
    return PyLong_FromLong(reclaimed);
}
1090 1091 1092


static /*const*/ PyMethodDef pybigfile_modulemeths[] = {
1093
    {"ram_reclaim", pyram_reclaim,  METH_VARARGS,   pyram_reclaim_doc},
1094 1095 1096 1097
    {NULL}
};


1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111

/* GIL hooks for virtmem big lock */
static void *py_gil_ensure_unlocked(void)
{
    /* make sure we don't hold python GIL (not to deadlock, as GIL oscillates)
     *
     * NOTE it is ok to get _PyThreadState_Current even without holding py gil -
     *      we only need to check whether ts_current != ts_my, and thus if this
     *      thread don't hold the gil, _PyThreadState_Current will be != ts_my
     *      for sure.
     *
     * NOTE2 we don't call PyThreadState_Get() as that thinks it is a bug when
     *       _PyThreadState_Current == NULL */
    PyThreadState *ts_my        = PyGILState_GetThisThreadState();
1112
    PyThreadState *ts_current   = _PyThreadState_UncheckedGet();
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
    PyThreadState *ts;

    if (ts_my && (ts_my == ts_current)) {
        ts = PyEval_SaveThread();
        BUG_ON(ts != ts_my);

        return ts_my;
    }

    return NULL;
}

static void py_gil_retake_if_waslocked(void *arg)
{
    PyThreadState *ts_my = (PyThreadState *)arg;

    /* retake GIL if we were holding it originally */
    PyEval_RestoreThread(ts_my);
}


static const VirtGilHooks py_virt_gil_hooks = {
    .gil_ensure_unlocked        = py_gil_ensure_unlocked,
    .gil_retake_if_waslocked    = py_gil_retake_if_waslocked,
};


1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
/* module init */
#if PY_MAJOR_VERSION >= 3
static /*const*/ PyModuleDef pybigfile_moduledef = {
    PyModuleDef_HEAD_INIT,
    .m_name     = "_bigfile",
    .m_size     = -1,   /* = disable creating several instances of this module */
    .m_methods  = pybigfile_modulemeths,
};
#endif

static PyObject *
_init_bigfile(void)
{
    PyObject *m;
1154 1155
    int err;

1156 1157 1158 1159 1160
    /* verify we copied struct PyBufferObject definition ok */
#if PY_MAJOR_VERSION < 3
    BUG_ON(sizeof(PyBufferObject) != PyBuffer_Type.tp_basicsize);
#endif

1161 1162 1163
    /* setup virtmem gil hooks for python */
    virt_lock_hookgil(&py_virt_gil_hooks);

1164 1165 1166 1167 1168
    /* setup pagefault handler right from the beginning - memory lazy-access
     * fundamentally depends on it */
    err = pagefault_init();
    if (err)
        Py_FatalError("bigfile: can't initialise pagefaulting");
1169 1170 1171 1172 1173 1174 1175 1176 1177

#if PY_MAJOR_VERSION >= 3
    m = PyModule_Create(&pybigfile_moduledef);
#else
    m = Py_InitModule("_bigfile", pybigfile_modulemeths);
#endif
    if (!m)
        return NULL;

1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197
    /* NOTE we don't expose VMA & BigFileH to users via module namespace */
    if (PyType_Ready(&PyVMA_Type) < 0)
        return NULL;
    if (PyType_Ready(&PyBigFileH_Type) < 0)
        return NULL;

    if (PyType_Ready(&PyBigFile_Type) < 0)
        return NULL;
    Py_INCREF(&PyBigFile_Type);
    if (PyModule_AddObject(m, "BigFile", (PyObject *)&PyBigFile_Type))
        return NULL;

#define CSTi(name)  do {                            \
    if (PyModule_AddIntConstant(m, #name, name))    \
        return NULL;                                \
} while (0)

    CSTi(WRITEOUT_STORE);
    CSTi(WRITEOUT_MARKSTORED);

1198 1199 1200 1201 1202
    /* import gc */
    gcmodule = PyImport_ImportModule("gc");
    if (!gcmodule)
        return NULL;

1203 1204 1205 1206
    pybuf_str = PyUnicode_FromString("<pybuf>");
    if (!pybuf_str)
        return NULL;

1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
    return m;
}


#if PY_MAJOR_VERSION < 3
# define PyInit__bigfile init_bigfile
#endif
__attribute__((visibility("default")))  // XXX should be in PyMODINIT_FUNC
PyMODINIT_FUNC
PyInit__bigfile(void)
{
#if PY_MAJOR_VERSION >= 3
    return
#endif
        _init_bigfile();
}
1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236


static void
XPyErr_SetFromErrno(void)
{
    PyObject *exc;

    switch(errno) {
        case ENOMEM:    exc = PyExc_MemoryError; break;
        default:        exc = PyExc_RuntimeError;
    }

    PyErr_SetFromErrno(exc);
}
1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251


static void
XPyErr_FullClear(void)
{
    PyObject  *x_curexc_type,    *x_curexc_value,    *x_curexc_traceback;
    PyObject  *x_exc_type,       *x_exc_value,       *x_exc_traceback;
    PyObject  *x_async_exc;
    PyThreadState *ts;

    ts = PyThreadState_GET();

    x_curexc_type       = set0(&ts->curexc_type);
    x_curexc_value      = set0(&ts->curexc_value);
    x_curexc_traceback  = set0(&ts->curexc_traceback);
1252 1253 1254 1255 1256 1257 1258
#if BIGFILE_USE_PYTS_EXC_INFO
    /* NOTE clearing top-level exc_state; if there is an active generator
     * spawned - its exc state is preserved. */
    x_exc_type          = set0(&ts->exc_state.exc_type);
    x_exc_value         = set0(&ts->exc_state.exc_value);
    x_exc_traceback     = set0(&ts->exc_state.exc_traceback);
#else
1259 1260 1261
    x_exc_type          = set0(&ts->exc_type);
    x_exc_value         = set0(&ts->exc_value);
    x_exc_traceback     = set0(&ts->exc_traceback);
1262
#endif
1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
    x_async_exc         = set0(&ts->async_exc);

    Py_XDECREF(x_curexc_type);
    Py_XDECREF(x_curexc_value);
    Py_XDECREF(x_curexc_traceback);
    Py_XDECREF(x_exc_type);
    Py_XDECREF(x_exc_value);
    Py_XDECREF(x_exc_traceback);
    Py_XDECREF(x_async_exc);
}
1273

1274 1275
static PyObject* /* PyListObject* */
XPyObject_GetReferrers(PyObject *obj)
1276 1277 1278
{
    PyObject *obj_referrers = PyObject_CallMethod(gcmodule, "get_referrers", "O", obj);
    BUG_ON(!obj_referrers);
1279 1280 1281 1282
    BUG_ON(!PyList_CheckExact(obj_referrers));
    return /*(PyListObject *)*/obj_referrers;
}

1283
void
1284 1285 1286
XPyObject_PrintReferrers(PyObject *obj, FILE *fp)
{
    PyObject *obj_referrers = XPyObject_GetReferrers(obj);
1287 1288 1289
    PyObject_Print(obj_referrers, fp, 0);
    Py_DECREF(obj_referrers);
}
1290 1291 1292 1293 1294 1295 1296 1297 1298 1299

static int
XPyFrame_IsCalleeOf(PyFrameObject *f, PyFrameObject *top)
{
    for (; f; f = f->f_back)
        if (f == top)
            return 1;

    return 0;
}
1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319

#if PY_MAJOR_VERSION < 3
void
XPyBufferObject_Unpin(PyBufferObject *bufo)
{
    bufo->b_ptr    = NULL;
    bufo->b_size   = 0;
    bufo->b_offset = 0;
    bufo->b_hash   = -1;
    Py_CLEAR(bufo->b_base);
}
#endif

void
XPyBuffer_Unpin(Py_buffer *view)
{
    view->buf = NULL;
    view->len = 0;
    Py_CLEAR(view->obj);
}