Mercurial > scratch
view pydershell/pydershell.py @ 15:351525e95a45
Added memory leak detection.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Sun, 06 Sep 2009 14:59:58 -0700 |
parents | 9bcbf780581f |
children | a78570a423ea |
line wrap: on
line source
#! /usr/bin/env python import sys import time import threading import traceback import weakref import pydermonkey class ContextWatchdogThread(threading.Thread): """ Watches active JS contexts and triggers their operation callbacks at a regular interval. """ DEFAULT_INTERVAL = 0.25 def __init__(self, interval=DEFAULT_INTERVAL): threading.Thread.__init__(self) self._lock = threading.Lock() self._stop = threading.Event() self._contexts = [] self.interval = interval self.setDaemon(True) def add_context(self, cx): self._lock.acquire() try: self._contexts.append(weakref.ref(cx)) finally: self._lock.release() def join(self): self._stop.set() threading.Thread.join(self) def run(self): while not self._stop.isSet(): time.sleep(self.interval) new_list = [] self._lock.acquire() try: for weakcx in self._contexts: cx = weakcx() if cx: new_list.append(weakcx) cx.trigger_operation_callback() self._contexts = new_list finally: self._lock.release() # Create a global watchdog. watchdog = ContextWatchdogThread() watchdog.start() class InternalError(BaseException): """ Represents an error in a JS-wrapped Python function that wasn't expected to happen; because it's derived from BaseException, it unrolls the whole JS/Python stack so that the error can be reported to the outermost calling code. """ def __init__(self): BaseException.__init__(self) self.exc_info = sys.exc_info() def safejsfunc(cx, on_obj, name=None): """ Exposes the decorated Python function on the given JS object. Any unexpected exceptions raised by the function will be re-raised as InternalError exceptions. """ def make_wrapper(func): if name is None: func_name = func.__name__ else: func_name = name def wrapper(func_cx, this, args): try: return func(func_cx, this, args) except pydermonkey.error: raise except Exception: raise InternalError() cx.define_property( on_obj, func_name, cx.new_function(wrapper, func_name) ) return func return make_wrapper def format_stack(js_stack): """ Returns a formatted Python-esque stack traceback of the given JS stack. """ STACK_LINE =" File \"%(filename)s\", line %(lineno)d, in %(name)s" lines = [] while js_stack: script = js_stack['script'] function = js_stack['function'] if script: frameinfo = dict(filename = script.filename, lineno = js_stack['lineno'], name = '<module>') elif function and not function.is_python: frameinfo = dict(filename = function.filename, lineno = js_stack['lineno'], name = function.name) else: frameinfo = None if frameinfo: lines.insert(0, STACK_LINE % frameinfo) js_stack = js_stack['caller'] lines.insert(0, "Traceback (most recent call last):") return '\n'.join(lines) class JSRuntime(object): """ A JS runtime capable of loading and executing scripts. """ def __init__(self, watchdog=watchdog): rt = pydermonkey.Runtime() cx = rt.new_context() globalobj = cx.new_object() cx.init_standard_classes(globalobj) @safejsfunc(cx, globalobj) def foo(cx, this, args): return cx.call_function(this, args[0], ()) @safejsfunc(cx, globalobj, 'print') def jsprint(cx, this, args): if len(args) > 0: print args[0] cx.set_operation_callback(self._opcb) cx.set_throw_hook(self._throwhook) watchdog.add_context(cx) self.rt = rt self.cx = cx self.globalobj = globalobj self.curr_exc = None self.py_stack = None self.js_stack = None def _opcb(self, cx): # Don't do anything; if a keyboard interrupt was triggered, # it'll get raised here automatically. pass def _throwhook(self, cx): curr_exc = cx.get_pending_exception() if self.curr_exc != curr_exc: self.curr_exc = curr_exc self.py_stack = traceback.extract_stack() self.js_stack = cx.get_stack() def run_script(self, filename): contents = open(filename).read() cx = self.cx try: cx.evaluate_script(self.globalobj, contents, filename, 1) except pydermonkey.error, e: print format_stack(self.js_stack) print e.args[1] except InternalError, e: print "An internal error occurred." traceback.print_tb(e.exc_info[2]) print e.exc_info[1] if __name__ == '__main__': runtime = JSRuntime() runtime.run_script('test.js') del runtime import gc gc.collect() if pydermonkey.get_debug_info()['runtime_count']: print "WARNING: JS runtime was not destroyed."