[SciPy-dev] reimplementation of lfilter

Sturla Molden sturla at molden.no
Wed Sep 23 21:10:45 EDT 2009


Charles R Harris skrev:
> It might be useful to think up some way that setjmp/longjmp could be 
> used in some sort of standard error handling template.
>

One way is to use a resource pool (a la Apache), see Cython code below.

cdef int _foobar() nogil:
    cdef  mempool *mp
    cdef int rv
    mp = mempool_init()
    if (mp == NULL):
        return -1 # error
    else:
        some_C_function(mp)
        mempool_destroy(mp)
        return int(rv)

def foobar():
    if (_foobar() < 0):
        raise MemoryError, 'malloc failed'

This alleviates any need for error checking on the return value from 
mempool_malloc. It might not be obvious though. What happens is that a 
NULL return form malloc trigger a longjmp back to mempool_init, after 
which the memory error is raised. Anything left allocated by the pool is 
freed on mempool_destroy, so it cannot leak. Pools can be used to manage 
any type of resource, e.g. open file handles, not just memory. The 
advantage is that you get C code that don't have to do error checking 
everywhere.

The problem with Cython is that a longjmp can mess up refcounts, so it 
probably should only be used in pure C mode, i.e. in a function safe to 
call with nogil.

C++ exceptions are much cleaner than C resource pools though, for 
example because destructors are called automatically. And the fact that 
C++ can put objects on the stack, and references can be used instead of 
pointers, means it is very safe against resource leaks if used 
correctly. The problems people have with C++ comes from bad style, for 
example calling new outside a constructor, using new[] instead of 
std::vector, using pointers instead of references (a reference cannot be 
dangling), etc.


Sturla Molden




# memory pool
# Copyright (C) 2009 Sturla Molden

import numpy as np
cimport numpy

cdef extern from "setjmp.h":
    ctypedef struct jmp_buf:
        pass
    int setjmp(jmp_buf jb) nogil
    void longjmp(jmp_buf jb, int errcode) nogil
  
cdef extern from "stdlib.h":
    # Assume malloc, realloc and free are thread safe
    # We may need to change this, in which case a lock
    # is needed in the memory pool.
    ctypedef unsigned long size_t
    void free(void *ptr) nogil
    void *malloc(size_t size) nogil
    void *realloc(void *ptr, size_t size) nogil
  
cdef struct mempool:
    mempool *prev
    mempool *next
  
cdef public mempool *mempool_init() nogil:
    cdef jmp_buf *jb
    cdef mempool *p = <mempool *> malloc(sizeof(jmp_buf) + sizeof(mempool))
    if not p: return NULL
    jb = <jmp_buf*>(<np.npy_intp>p + sizeof(mempool))
    p.prev = NULL
    p.next = NULL
    if setjmp(jb[0]):
        # mempool_error has been called
        mempool_destroy(p)
        return NULL
    else:
        return p
  
cdef public void mempool_error(mempool *p) nogil:
    # rewind stack back to mempool_init()
    cdef jmp_buf *jb = <jmp_buf*>(<np.npy_intp>p + sizeof(mempool))
    longjmp(jb[0], 1)

cdef public void *mempool_destroy(mempool *p) nogil:
    # this releases all memory allocated to this pool
    cdef mempool *tmp
    while p:
        tmp = p
        p = p.next
        free(tmp)

cdef public void *mempool_malloc(mempool *p, size_t n) nogil:
    cdef mempool *block
    cdef void *m = malloc(sizeof(mempool) + n)
    if not m:
        mempool_error(p)
    block = <mempool*> m
    block.next = p.next
    if block.next:
        block.next.prev = block
    block.prev = p
    p.next = block
    return <void*>(<np.npy_intp>m + sizeof(mempool))

cdef public void *mempool_realloc(mempool *p, void *ptr, size_t n) nogil:
    cdef void *retval
    cdef mempool *block, *prev, *next, *new_addr
    if not n:
        # realloc(p, 0) is free(p)
        mempool_free(ptr)
        retval = NULL
    elif not ptr:
        # realloc(0, n) is malloc(n)
        retval = mempool_malloc(p,n)
    else:
        # realloc(p, n)
        block = <mempool *>(<np.npy_intp>ptr - sizeof(mempool))
        prev = block.prev
        next = block.next
        new_addr = <mempool*> realloc(block, n + sizeof(mempool))
        if not new_addr:
            mempool_error(p)
        if new_addr != block:
            prev.next = new_addr
            if next:
                next.prev = new_addr
            new_addr.prev = prev
            new_addr.next = next
        retval = <void *>(<np.npy_intp>new_addr + sizeof(mempool))
    return retval

cdef public void *mempool_free(void *ptr) nogil:
    cdef mempool *prev, *next, *cur
    cdef mempool *block = <mempool *>(<np.npy_intp>ptr - sizeof(mempool))
    prev = block.prev
    next = block.next
    if next:
        free(block)
        next.prev = prev
        prev.next = next
    else:
        free(block)
        prev.next = NULL








More information about the SciPy-Dev mailing list