Commit b2309d92 authored by qsr's avatar qsr Committed by Commit bot

mojo: Add a runloop utility in python

This is necessary to be able to use async operation on handles.

This is a reland of https://codereview.chromium.org/552783004 after the
build dependency issue has been fixed.

R=sdefresne@chromium.org
BUG=415485

Review URL: https://codereview.chromium.org/577193002

Cr-Commit-Position: refs/heads/master@{#295451}
parent 9d0221cd
......@@ -566,10 +566,15 @@
},
'sources': [
'public/python/mojo/c_core.pxd',
'public/python/mojo/c_environment.pxd',
'public/python/mojo/system.pyx',
'public/python/src/python_system_helper.cc',
'public/python/src/python_system_helper.h',
],
'dependencies': [
'mojo_base.gyp:mojo_environment_standalone',
'mojo_base.gyp:mojo_system',
'mojo_base.gyp:mojo_utility',
],
'includes': [ '../third_party/cython/cython_compiler.gypi' ],
},
......
......@@ -17,10 +17,17 @@ python_binary_module("system") {
python_base_module = "mojo"
sources = [
"mojo/c_core.pxd",
"mojo/c_environment.pxd",
"mojo/system.pyx",
]
additional_sources = [
"src/python_system_helper.cc",
"src/python_system_helper.h",
]
deps = [
"//mojo/public/c/system",
"//mojo/public/cpp/environment:standalone",
"//mojo/public/cpp/utility",
":base",
]
}
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# distutils: language = c++
from libc.stdint cimport int64_t
cdef extern from "mojo/public/cpp/bindings/callback.h" nogil:
cdef cppclass CClosure "mojo::Callback<void()>":
CClosure()
cdef extern from "mojo/public/python/src/python_system_helper.h" \
namespace "mojo" nogil:
cdef CClosure BuildClosure(object)
cdef extern from "mojo/public/cpp/utility/run_loop.h" nogil:
cdef cppclass CRunLoop "mojo::RunLoop":
CRunLoop()
void Run() except *
void RunUntilIdle() except *
void Quit()
void PostDelayedTask(CClosure& task, int64_t delay)
cdef extern from "mojo/public/cpp/environment/environment.h" nogil:
cdef cppclass CEnvironment "mojo::Environment":
CEnvironment()
......@@ -5,6 +5,8 @@
# distutils language = c++
cimport c_core
cimport c_environment
from cpython.buffer cimport PyBUF_CONTIG
from cpython.buffer cimport PyBUF_CONTIG_RO
......@@ -694,3 +696,32 @@ class DuplicateSharedBufferOptions(object):
def __init__(self):
self.flags = DuplicateSharedBufferOptions.FLAG_NONE
cdef class RunLoop(object):
"""RunLoop to use when using asynchronous operations on handles."""
cdef c_environment.CRunLoop c_run_loop
def Run(self):
"""Run the runloop until Quit is called."""
self.c_run_loop.Run()
def RunUntilIdle(self):
"""Run the runloop until Quit is called or no operation is waiting."""
self.c_run_loop.RunUntilIdle()
def Quit(self):
"""Quit the runloop."""
self.c_run_loop.Quit()
def PostDelayedTask(self, runnable, delay=0):
"""
Post a task on the runloop. This must be called from the thread owning the
runloop.
"""
cdef c_environment.CClosure closure = c_environment.BuildClosure(runnable)
self.c_run_loop.PostDelayedTask(closure, delay)
cdef c_environment.CEnvironment* _ENVIRONMENT = new c_environment.CEnvironment()
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/public/python/src/python_system_helper.h"
#include "Python.h"
#include "mojo/public/cpp/environment/logging.h"
#include "mojo/public/cpp/system/macros.h"
#include "mojo/public/cpp/utility/run_loop.h"
namespace {
class ScopedGIL {
public:
ScopedGIL() {
state_ = PyGILState_Ensure();
}
~ScopedGIL() {
PyGILState_Release(state_);
}
private:
PyGILState_STATE state_;
MOJO_DISALLOW_COPY_AND_ASSIGN(ScopedGIL);
};
class PythonClosure : public mojo::Closure::Runnable {
public:
PythonClosure(PyObject* callable) : callable_(callable) {
MOJO_CHECK(callable);
Py_XINCREF(callable);
}
virtual ~PythonClosure() {
ScopedGIL acquire_gil;
Py_DECREF(callable_);
}
virtual void Run() const MOJO_OVERRIDE {
ScopedGIL acquire_gil;
PyObject* empty_tuple = PyTuple_New(0);
if (!empty_tuple) {
mojo::RunLoop::current()->Quit();
return;
}
PyObject* result = PyObject_CallObject(callable_, empty_tuple);
Py_DECREF(empty_tuple);
if (result) {
Py_DECREF(result);
} else {
mojo::RunLoop::current()->Quit();
return;
}
}
private:
PyObject* callable_;
MOJO_DISALLOW_COPY_AND_ASSIGN(PythonClosure);
};
} // namespace
namespace mojo {
Closure BuildClosure(PyObject* callable) {
if (!PyCallable_Check(callable))
return Closure();
return Closure(
static_cast<mojo::Closure::Runnable*>(new PythonClosure(callable)));
}
} // namespace mojo
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_PUBLIC_PYTHON_SRC_PYTHON_SYSTEM_HELPER_H_
#define MOJO_PUBLIC_PYTHON_SRC_PYTHON_SYSTEM_HELPER_H_
#include "Python.h"
#include "mojo/public/cpp/bindings/callback.h"
namespace mojo {
// Create a mojo::Closure from a callable python object.
mojo::Closure BuildClosure(PyObject* callable);
// Initalize mojo::RunLoop
void InitRunLoop();
} // namespace mojo
#endif // MOJO_PUBLIC_PYTHON_SRC_PYTHON_SYSTEM_HELPER_H_
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import unittest
# pylint: disable=F0401
import mojo.embedder
from mojo import system
def _Increment(array):
def _Closure():
array.append(0)
return _Closure
class RunLoopTest(unittest.TestCase):
def setUp(self):
mojo.embedder.Init()
def testRunLoop(self):
loop = system.RunLoop()
array = []
for _ in xrange(10):
loop.PostDelayedTask(_Increment(array))
loop.RunUntilIdle()
self.assertEquals(len(array), 10)
def testRunLoopWithException(self):
loop = system.RunLoop()
def Throw():
raise Exception("error")
array = []
loop.PostDelayedTask(Throw)
loop.PostDelayedTask(_Increment(array))
with self.assertRaisesRegexp(Exception, '^error$'):
loop.Run()
self.assertEquals(len(array), 0)
loop.RunUntilIdle()
self.assertEquals(len(array), 1)
......@@ -72,6 +72,9 @@ template("python_binary_module") {
datadeps = invoker.datadeps
}
sources = [ cython_output ]
if (defined(invoker.additional_sources)) {
sources += invoker.additional_sources
}
configs += [ ":$config_name" ]
}
......
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