Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Multiple inheritance support #410

Merged
merged 5 commits into from
Sep 19, 2016
Merged

WIP: Multiple inheritance support #410

merged 5 commits into from
Sep 19, 2016

Conversation

wjakob
Copy link
Member

@wjakob wjakob commented Sep 12, 2016

This is an early snapshot of a potential way to support multiple inheritance in a minimally intrusive way. Standard single inheritance is supported using a fast fallback that matches the current implementation.

Somewhat surprisingly, the change and related simplifications actually reduce the size of generated binaries by 4.4% :).

There are probably a ton of cases that I didn't think of, so this is up for public review now.

@wjakob
Copy link
Member Author

wjakob commented Sep 12, 2016

cc @jagerman @dean0x7d @aldanor @lyskov @pschella @BorisSchaeling

Your feedback would be greatly appreciated.

@@ -754,7 +794,7 @@ template <typename type, typename holder_type> class type_caster_holder : public
using type_caster_base<type>::value;
using type_caster_base<type>::temp;

bool load(handle src, bool convert) {
bool load(handle src, bool convert) {//XXX
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Multiple inheritance involving shared pointer classes TBD, just needs to be copied from above)

@lyskov
Copy link
Contributor

lyskov commented Sep 12, 2016

@wjakob support for multiple inheritance will certainly be welcome!!! I will be happy to test this on PyRosetta when it is merged (will require some time to implement it though) so please drop me a line when its ready for testing.

A few questions:

  • how in-Python overriding will work for such classes? Any special things needs to be done?
  • what behavior will be if derived class have bases with overlapping member functions? (i.e. if both base classes have foo methods)?


template <template<typename> class Predicate, typename Child>
struct add_base_helper<Predicate, Child> {
static void apply(detail::type_record &) { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't actually compiled the following snippet, so take it with a grain of salt, but it's a possible simplification (non-recursive, non-SFINAE):

template <typename Base, typename Child>
void add_base_helper(type_record &rec, std::true_type /*is_base*/) {
    rec.add_base(&typeid(Base), [](void *src) -> void * {
        return static_cast<Base *>(reinterpret_cast<Child *>(src));
    });
}

template <typename...> void add_base_helper(type_record &, std::false_type /*is_base*/) { }

Called as:

add_base_helper<options, type_>(record, is_base_class<options>())...

(Plus the usual variadic call {(f(), 0)...} dance.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's much better, thanks.

@wjakob
Copy link
Member Author

wjakob commented Sep 13, 2016

@wjakob support for multiple inheritance will certainly be welcome!!! I will be happy to test this on PyRosetta when it is merged (will require some time to implement it though) so please drop me a line when its ready for testing.

A few questions:

how in-Python overriding will work for such classes? Any special things needs to be done?
what behavior will be if derived class have bases with overlapping member functions? (i.e. if both base classes have foo methods)?

@lyskov: The goal of posting the WIP was to get other people to test drive this sufficiently before it is merged. Regarding your other questions: I think extending from Python should work just the same, though it would be good for somebody to verify this. A more tricky case would entail classes in Python that perform multiple inheritance from multiple C++ or Python classes -- I think that will run into limitations of the current patch set.

@wjakob
Copy link
Member Author

wjakob commented Sep 13, 2016

rebased and integrated feedback

@lyskov
Copy link
Contributor

lyskov commented Sep 13, 2016

@wjakob oh, so its ready for testing - great, i will give it a try then, i will let you know how it goes.

@lyskov
Copy link
Contributor

lyskov commented Sep 13, 2016

@wjakob not sure if it is possible to catch this during compilation: right now specifying the same base type multiple times lead to cryptic runtime error during runtime: ImportError: generic_type: PyType_Ready failed!

@lyskov
Copy link
Contributor

lyskov commented Sep 13, 2016

Also, the same kind of error appears if bases was specified once but there was a virtual inheritance somewhere before...

@lyskov
Copy link
Contributor

lyskov commented Sep 13, 2016

I am guessing that for cases of virtual inheritance we should specify virtual bases only once? Is this the right way?

@wjakob
Copy link
Member Author

wjakob commented Sep 14, 2016

@lyskov: For multiple inheritance, could you post the source code snippet/class_<> instantiation that made it crash?

@lyskov
Copy link
Contributor

lyskov commented Sep 14, 2016

Yes, i already working on minimal example for this...

@lyskov
Copy link
Contributor

lyskov commented Sep 14, 2016

code example to trigger this error with just multiple bases:

#include <pybind11/pybind11.h>

class Common {};
class Base1 : public virtual Common {};
PYBIND11_PLUGIN(example) {
    pybind11::module m("example", "pybind11 example plugin");
    {
        pybind11::class_<Common> cl(m, "Common");
        cl.def(pybind11::init<>());
    }
    {
        pybind11::class_<Base1, Common, Common> cl(m, "Base1"); // Deliberately specifying base class twice - this will lead to an error

        cl.def(pybind11::init<>());
    }
    return m.ptr();
}

@lyskov
Copy link
Contributor

lyskov commented Sep 14, 2016

And a bit more complex example when using virtual inheritance:

While this works:

#include <pybind11/pybind11.h>
class Common {};
class Base1 : public virtual Common {};
class Base2 : public virtual Common {};
class Derived : public Base1, public Base2, public virtual Common {};
PYBIND11_PLUGIN(example) {
    pybind11::module m("example", "pybind11 example plugin");
    {
        pybind11::class_<Common> cl(m, "Common");
        cl.def(pybind11::init<>());
    }
    {
        pybind11::class_<Base1, Common> cl(m, "Base1");

        cl.def(pybind11::init<>());
    }
    {
        pybind11::class_<Base2, Common> cl(m, "Base2");
        cl.def(pybind11::init<>());
    }
    {
        pybind11::class_<Derived, Base1, Base2, Common> cl(m, "Derived");
        cl.def(pybind11::init<>());
    }
    return m.ptr();
}

This does not (i know it rather silly, but right now i could not extract the real one from my code base that trigger the same error):

#include <pybind11/pybind11.h>

class Base1 {};
class Base2 : public virtual Base1 {};
class Derived : public virtual Base1, public Base2 {};

PYBIND11_PLUGIN(example) {
    pybind11::module m("example", "pybind11 example plugin");

    {
        pybind11::class_<Base1> cl(m, "Base1");
        cl.def(pybind11::init<>());
    }

    {
        pybind11::class_<Base2, Base1> cl(m, "Base2");
        cl.def(pybind11::init<>());
    }

    {
        pybind11::class_<Derived, Base1, Base2> cl(m, "Derived");
        cl.def(pybind11::init<>());
    }

    return m.ptr();
}

Edit: posted the missing code

@lyskov
Copy link
Contributor

lyskov commented Sep 14, 2016

Would it be possible to generate more informative error message during import? Because right now it only state either ImportError: generic_type: PyType_Ready failed! or ImportError: generic_type: specified base type multiple times! none of which is really helpful...

@wjakob
Copy link
Member Author

wjakob commented Sep 14, 2016

Yep, the error messages could be a bit more clear, I'll take a stab at that. Note that specifying the same base class multiple times to class_<> is definitely not going to work.

Regarding virtual inheritance, see the following (in the context of Boost.Python bindings): http://stackoverflow.com/questions/21221628/virtual-inheritance-c-and-boost-python
This suggests to me that we'll not be able to support this case.

@lyskov
Copy link
Contributor

lyskov commented Sep 14, 2016

Re virtual inheritance: Interesting, - do you have clear idea what will work and what will not? Because it does not look like it is the 'diamond case' since the following example is seems to work:

#include <pybind11/pybind11.h>

class Common {};
class Base1 : public virtual Common {};
class Base2 : public virtual Common {};
class Derived : public Base1, public Base2 {};

PYBIND11_PLUGIN(example) {
    pybind11::module m("example", "pybind11 example plugin");
    {
        pybind11::class_<Common> cl(m, "Common");
        cl.def(pybind11::init<>());
    }
    {
        pybind11::class_<Base1, Common> cl(m, "Base1");
        cl.def(pybind11::init<>());
    }
    {
        pybind11::class_<Base2, Common> cl(m, "Base2");
        cl.def(pybind11::init<>());
    }
    {
        pybind11::class_<Derived, Base1, Base2> cl(m, "Derived");
        cl.def(pybind11::init<>());
    }
    return m.ptr();
}

It would be great if we can formulate formal definition what can and what will not work so i can put it into code for bindings generator.

@wjakob
Copy link
Member Author

wjakob commented Sep 14, 2016

@lyskov

The error messages are now a bit more verbose: In your previous example, the output is

ImportError: Derived: PyType_Ready failed (TypeError: Cannot create a consistent method resolution order (MRO) for bases Base1, Base2)!

It turns out that Python just does not allow this kind of class hierarchy (try creating without C++ in pure Python).

@wjakob
Copy link
Member Author

wjakob commented Sep 14, 2016

There is now a bit of documentation which discusses the syntax and limitations


.. code-block:: cpp

py::class_<MyType, BaseType2>(m, "MyType", py::multiple_inheritance());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be nicer to add this as a template option annotation, rather than going through the process_attribute mechanism, so that it stays with the base classes, i.e.:

py::class_<MyType, py::multiple_inheritance, BaseType2>(...)

That is pretty easy to do: just add a new predicate for the type to the list of predicates at the beginning of class_ like this:

    template <typename T> using is_mi_annotation = std::is_same<T, multiple_inheritance>;

then update the is_valid_class_option predicate with || is_mi_annotation<T>::value, and finally, add this to the class_ constructor:

    if (detail::any_of_t<is_mi, options...>::value) record.multiple_inheritance = true;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since multiple_inheritance doesn't really parameterize the type of the class (not a base/alias/holder), I would find it consistent to keep the marker in the (non-template) argument list similar to how such flags are used for method definitions. Other opinions?

@jagerman
Copy link
Member

Re: multiple inheritance on the python side, the documentation current says that you can't, and that this is a limitation of Python. That's true, in a sense, but I think there's a potential workaround.

While true, it is actually something that seems, in theory, as though it could be worked around, by making pybind11 classes lay-out compatible. I haven't actually tried this, but it seems to me that if every pybind-registered type consisted of, say, just a single pointer (i.e. to the real instance, stored somewhere in pybind11-controlled memory), classes would have a compatible layout, and Python would allow the multiple inheritance. (It might be necessary to have more than one pointer; e.g. a second pointer to type information, but that should still be layout compatible as long as every class has the same thing).

That would, of course, require pybind11 to have a layer of pointer redirection: methods would need to go to a (very hot) pybind11 dispatch function that invokes the actual function with the dereferenced pointer, but unless I'm missing something, it seems like a feasible approach.

(Of course, I think it's way beyond the scope of the current PR, at least as currently implemented, which is about allowing MI on the C++ side).

2. As was previously discussed in the section on :ref:`overriding_virtuals`, it
is easy to create Python types that derive from C++ classes. It is even
possible to make use of multiple inheritance to declare a Python class which
has e.g. a C++ and a Python class as bases. However, any attempt to create
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit more limited than that: you can't have two different C++ classes anywhere in your inheritance tree; e.g. having a C++ and Python classes as bases won't work if the Python class itself depends on a C++ class (or another Python class that depends on a C++ class, ad infinitum).

@wjakob
Copy link
Member Author

wjakob commented Sep 16, 2016

Great catch and nice workaround @dean0x7d!
I've rebased onto master and applied your change.

@wjakob
Copy link
Member Author

wjakob commented Sep 16, 2016

Update: looks like that didn't fix it.

@dean0x7d
Copy link
Member

I've just double checked that it really is an issue specific to old-style classes: this change bef164f results in a clean CI build. Note that test_multiple_inheritance_mix2 is perfectly fine even with the old-style class, so that may be a clue.

tp_bases is not the problem as I initially thought. It did fix the issue for me yesterday, but it seems to have been a coincidence. And funny enough, I can't even reproduce the original issue any more today. Definitely some uninitialized variable. Here is the crash log from the time I managed to make it crash with a debug build:

0   pybind11_tests.so               0x0000000103ce382a pybind11::handle::inc_ref() const + 42 (pytypes.h:31)
1   pybind11_tests.so               0x0000000103ce37f4 pybind11::object::object(pybind11::handle const&, bool) + 68 (pytypes.h:64)
2   pybind11_tests.so               0x0000000103ce37a7 pybind11::str::str(pybind11::handle const&, bool) + 55 (pybind11.h:526)
3   pybind11_tests.so               0x0000000103ce371c pybind11::str::str(pybind11::handle const&, bool) + 44 (pybind11.h:526)
4   pybind11_tests.so               0x0000000103cfea16 pybind11::detail::type_caster_generic::load(pybind11::handle, bool, _typeobject*) + 870 (cast.h:188)
5   pybind11_tests.so               0x0000000103cfeb5a pybind11::detail::type_caster_generic::load(pybind11::handle, bool, _typeobject*) + 1194 (cast.h:189)
6   pybind11_tests.so               0x0000000103cfe69a pybind11::detail::type_caster_generic::load(pybind11::handle, bool) + 90 (cast.h:160)
7   pybind11_tests.so               0x0000000103d04ee0 bool pybind11::detail::type_caster<std::__1::tuple<ScopedEnum&, int>, void>::load<0ul, 1ul>(pybind11::handle, bool, pybind11::detail::index_sequence<0ul, 1ul>) + 112 (cast.h:763)
8   pybind11_tests.so               0x0000000103d04d1c bool pybind11::detail::type_caster<std::__1::tuple<ScopedEnum&, int>, void>::load_args<std::__1::tuple<ScopedEnum, int>, 0>(pybind11::handle, pybind11::handle, bool) + 60 (cast.h:708)
9   pybind11_tests.so               0x0000000103fc7d45 void pybind11::cpp_function::initialize<void pybind11::detail::init<int>::execute<pybind11::class_<Base2>, 0>(pybind11::class_<Base2>&)::'lambda'(Base2*, int), void, Base2*, int, pybind11::name, pybind11::sibling, pybind11::is_method>(pybind11::class_<Base2>&&, void (*)(Base2*, int), pybind11::name const&, pybind11::sibling const&, pybind11::is_method const&)::'lambda'(pybind11::detail::function_record*, pybind11::handle, pybind11::handle, pybind11::handle)::operator()(pybind11::detail::function_record*, pybind11::handle, pybind11::handle, pybind11::handle) const + 133 (pybind11.h:122)
10  pybind11_tests.so               0x0000000103fc7ca8 void pybind11::cpp_function::initialize<void pybind11::detail::init<int>::execute<pybind11::class_<Base2>, 0>(pybind11::class_<Base2>&)::'lambda'(Base2*, int), void, Base2*, int, pybind11::name, pybind11::sibling, pybind11::is_method>(pybind11::class_<Base2>&&, void (*)(Base2*, int), pybind11::name const&, pybind11::sibling const&, pybind11::is_method const&)::'lambda'(pybind11::detail::function_record*, pybind11::handle, pybind11::handle, pybind11::handle)::__invoke(pybind11::detail::function_record*, pybind11::handle, pybind11::handle, pybind11::handle) + 72 (pybind11.h:118)
11  pybind11_tests.so               0x0000000103cfff39 pybind11::cpp_function::dispatcher(_object*, _object*, _object*) + 2041 (pybind11.h:422)

The pybind11::detail::type_caster_generic::load lines made me suspect tp_bases. I don't really know why py::str is there.

@wjakob
Copy link
Member Author

wjakob commented Sep 17, 2016

This latest patch seems to have fixed it!

@lyskov
Copy link
Contributor

lyskov commented Sep 17, 2016

Just tried latest version on my project: i am still receiving Fatal Python error: type_traverse() called for non-heap type 'rosetta.core.scoring.func.USOGFunc' on regular (non MI class_) class binding...

@wjakob
Copy link
Member Author

wjakob commented Sep 17, 2016

This sounds like a fairly bizarre failure (though AFAIK nobody else has run into it). I would be happy to try to track it down if you could turn this into a reproducible test case.

@wjakob
Copy link
Member Author

wjakob commented Sep 17, 2016

(Also, I assume this happens even without PR #410, right?)

@lyskov
Copy link
Contributor

lyskov commented Sep 17, 2016

(Also, I assume this happens even without PR #410, right?)

i was wrong about this. After double checking i am sure that this issue arise only when build with #410 version.

Is there is anything i can do to help track this down? Standard methods of isolation is not very useful here since this error happened after ~800+ classes was bound and class_ declaration for it look innocent enough. Is there is way to obtain traceback that would be helpful to debug this?

Edit: remove markup for clarity

@wjakob
Copy link
Member Author

wjakob commented Sep 17, 2016

A backtrace might help here. With a debug build, you could launch Python in a debugger and break on "__cxa_throw" to get a backtrace.

@lyskov
Copy link
Contributor

lyskov commented Sep 17, 2016

Do i need Python debug build for this as well?

@wjakob
Copy link
Member Author

wjakob commented Sep 17, 2016

That would help even more since assertions might catch another error before that obscures the real reason.

@lyskov
Copy link
Contributor

lyskov commented Sep 18, 2016

here backtrace for this, hope this helps!

gdb$ bt
#0  0x00007ffff6c5a5d7 in raise () from /lib64/libc.so.6
#1  0x00007ffff6c5bcc8 in abort () from /lib64/libc.so.6
#2  0x00007ffff7a517ff in Py_FatalError () from /lib64/libpython3.5m.so.1.0
#3  0x00007ffff79cc6f5 in type_traverse () from /lib64/libpython3.5m.so.1.0
#4  0x00007ffff7a6ca47 in collect () from /lib64/libpython3.5m.so.1.0
#5  0x00007ffff7a6d5fd in collect_with_callback () from /lib64/libpython3.5m.so.1.0
#6  0x00007ffff7a6d7c7 in _PyObject_GC_Alloc () from /lib64/libpython3.5m.so.1.0
#7  0x00007ffff7a6de6a in _PyObject_GC_NewVar () from /lib64/libpython3.5m.so.1.0
#8  0x00007ffff79c9ed7 in PyTuple_New () from /lib64/libpython3.5m.so.1.0
#9  0x00007fffd2cacb8b in pybind11::tuple::tuple (this=0x7fffffff8c70, size=0x1) at /home/benchmark/src/pybind11/include/pybind11/pytypes.h:582
#10 0x00007fffd2cb6c69 in pybind11::detail::generic_type::initialize (this=0x7fffffff9910, rec=0x7fffffff8e78) at /home/benchmark/src/pybind11/include/pybind11/pybind11.h:641
#11 0x00007fffd61f1a21 in pybind11::class_<core::chemical::AddBondType, std::shared_ptr<core::chemical::AddBondType>, PyCallBack_AddBondType, core::chemical::PatchOperation>::class_<char [241]> (this=0x7fffffff9910, scope=..., name=0x7fffe3d66902 "AddBondType", extra=...) at /home/benchmark/src/pybind11/include/pybind11/pybind11.h:870
#12 0x00007fffd61ecbf9 in bind_core_chemical_PatchOperation_1(std::function<pybind11::module& (std::string const&)>&) (M=...) at /home/benchmark/rosetta/binder/main/source/build/PyRosetta/linux/clang/pyhton-3.5/debug/source/core/chemical/PatchOperation_1.cpp:2050
#13 0x00007fffd2c8e15f in pybind11_init () at /home/benchmark/rosetta/binder/main/source/build/PyRosetta/linux/clang/pyhton-3.5/debug/source/rosetta.cpp:2574
#14 0x00007fffd2c86bdd in PyInit_rosetta () at /home/benchmark/rosetta/binder/main/source/build/PyRosetta/linux/clang/pyhton-3.5/debug/source/rosetta.cpp:1863
#15 0x00007ffff7a4980e in _PyImport_LoadDynamicModuleWithSpec () from /lib64/libpython3.5m.so.1.0
#16 0x00007ffff7a47a0b in _imp_create_dynamic () from /lib64/libpython3.5m.so.1.0
#17 0x00007ffff79bb249 in PyCFunction_Call () from /lib64/libpython3.5m.so.1.0
#18 0x00007ffff7a33482 in PyEval_EvalFrameEx () from /lib64/libpython3.5m.so.1.0
#19 0x00007ffff7a3484c in _PyEval_EvalCodeWithName () from /lib64/libpython3.5m.so.1.0
#20 0x00007ffff7a31ab6 in PyEval_EvalFrameEx () from /lib64/libpython3.5m.so.1.0
#21 0x00007ffff7a32878 in PyEval_EvalFrameEx () from /lib64/libpython3.5m.so.1.0
#22 0x00007ffff7a32878 in PyEval_EvalFrameEx () from /lib64/libpython3.5m.so.1.0
#23 0x00007ffff7a32878 in PyEval_EvalFrameEx () from /lib64/libpython3.5m.so.1.0
#24 0x00007ffff7a32878 in PyEval_EvalFrameEx () from /lib64/libpython3.5m.so.1.0
#25 0x00007ffff7a3484c in _PyEval_EvalCodeWithName () from /lib64/libpython3.5m.so.1.0
#26 0x00007ffff7a34958 in PyEval_EvalCodeEx () from /lib64/libpython3.5m.so.1.0
#27 0x00007ffff799e7fe in function_call () from /lib64/libpython3.5m.so.1.0
#28 0x00007ffff7973daa in PyObject_Call () from /lib64/libpython3.5m.so.1.0
#29 0x00007ffff7974810 in _PyObject_CallMethodIdObjArgs () from /lib64/libpython3.5m.so.1.0
#30 0x00007ffff7a48bd3 in PyImport_ImportModuleLevelObject () from /lib64/libpython3.5m.so.1.0
#31 0x00007ffff7a272ef in builtin___import__ () from /lib64/libpython3.5m.so.1.0
#32 0x00007ffff79bb269 in PyCFunction_Call () from /lib64/libpython3.5m.so.1.0
#33 0x00007ffff7973daa in PyObject_Call () from /lib64/libpython3.5m.so.1.0
#34 0x00007ffff7a2ae37 in PyEval_CallObjectWithKeywords () from /lib64/libpython3.5m.so.1.0
#35 0x00007ffff7a2d474 in PyEval_EvalFrameEx () from /lib64/libpython3.5m.so.1.0
#36 0x00007ffff7a3484c in _PyEval_EvalCodeWithName () from /lib64/libpython3.5m.so.1.0
#37 0x00007ffff7a34958 in PyEval_EvalCodeEx () from /lib64/libpython3.5m.so.1.0
#38 0x00007ffff7a3499b in PyEval_EvalCode () from /lib64/libpython3.5m.so.1.0
#39 0x00007ffff7a53d04 in run_mod () from /lib64/libpython3.5m.so.1.0
#40 0x00007ffff7a555d5 in PyRun_StringFlags () from /lib64/libpython3.5m.so.1.0
#41 0x00007ffff7a5563b in PyRun_SimpleStringFlags () from /lib64/libpython3.5m.so.1.0
#42 0x00007ffff7a6bb07 in Py_Main () from /lib64/libpython3.5m.so.1.0
#43 0x0000000000400ad9 in main ()

@lyskov
Copy link
Contributor

lyskov commented Sep 18, 2016

A bit of extra info: - I can not reproduce this on mac with Python-2.7. So it might be possible that problem is Python-3 specific or only appear on Linux...

@wjakob
Copy link
Member Author

wjakob commented Sep 18, 2016

That backtrace was indeed helpful (and reminiscent of the old bug #277). I've committed a fix, can you see if it works?

@wjakob
Copy link
Member Author

wjakob commented Sep 18, 2016

It's great that you have a codebase with so many classes. These kind of GC-in-an-unfortunate-place issues are so tricky to track down with just the default test suite.

@wjakob
Copy link
Member Author

wjakob commented Sep 18, 2016

Assuming this fixes the issue reported by @lyskov, my plan is to merge this in the next couple of days. Speak now or forever hold your peace (or something like that ;)).

@lyskov
Copy link
Contributor

lyskov commented Sep 18, 2016

Glad to hear that backtrace was helpful! I can confirm that c5c8b96 is indeed fixed the issue!

I am now in process of running rest of my tests and will post here when its done (should take ~24hr or less)

@lyskov
Copy link
Contributor

lyskov commented Sep 19, 2016

Just letting you know that I am finished testing this PR with my codebase and so far have not found any more issues, - I am looking forward to see this code in master!

@wjakob
Copy link
Member Author

wjakob commented Sep 19, 2016

Great, good to hear! Squashed into a smaller number of commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants