This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Clean up and fix OrderedDict
Type: behavior Stage: patch review
Components: Extension Modules Versions: Python 3.6, Python 3.5
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: bup, cheryl.sabella, eric.snow, python-dev, rhettinger, serhiy.storchaka
Priority: normal Keywords: patch

Created on 2015-10-15 08:05 by serhiy.storchaka, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
odict_cleanup.patch serhiy.storchaka, 2015-10-15 08:05 review
odict_cleanup_2.patch serhiy.storchaka, 2015-10-17 19:23 review
odict_type.patch serhiy.storchaka, 2015-10-18 08:24 review
odict_add_new_node_leak.patch serhiy.storchaka, 2015-10-20 13:41 review
odict_revert_setting_on_error.patch serhiy.storchaka, 2015-10-22 18:48 review
odict_iternext_simpler.patch serhiy.storchaka, 2015-10-22 18:53 review
odict_resize_sentinel.patch serhiy.storchaka, 2015-10-22 19:14 review
odict_copy_iter.patch serhiy.storchaka, 2015-12-25 12:21 review
Messages (35)
msg253033 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-15 08:05
Proposed patch cleans up and fixes minor bugs in C implementation of OrderedDict.

1. Used the "p" format unit instead of manual calling PyObject_True() for parsing boolean parameters.

2. Used _Py_Identifier private API instead of char* API if appropriate.

3. Used Py_TYPE instead of the __class__ attribute as in other extension types.

4. Fixed od_fast_nodes size calculation in  __sizeof__().

5. Simplified __reduce__() taking into account that __dict__ is empty in C implementation of OrderedDict.

6. popitem() with wrong number of arguments now raises TypeError instead of KeyError for empty dictionary.

7. Python implementation of move_to_end() calls key comparing only once in common case. C implementation called key comparing twice, it first compares a key with first or last key. Now C implementation calls key comparing only once in common case.

8. Used PyUnicode_FromFormat() instead of str.__mod__ in __repr__().

9. update() now takes into account that args and kwargs are always tuple and dict (if not NULL).

10. Got rid of PyMapping_Items() in update(). PyMapping_Items() creates a copy of items as a list, this is not needed.

Also applied other cleanups. The size of sources is decreased by 105 lines.

 Objects/odictobject.c |  194 +-----------------------------!!!!!!!!!!!!!!!!!!
 1 file changed, 6 insertions(+), 111 deletions(-), 77 modifications(!)
msg253051 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2015-10-15 14:59
These all look good except for perhaps #5 which I need to look at a bit more for its effect on OD subclasses.
msg253110 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-17 01:06
Thanks for working on this, Serhiy.  I've left a review.

As to the points you outlined, I have concerns with the impact of #3 and #5 on subclasses.  Notably od.__class__ is not necessarily the same as type(od).  Also #7 may introduce an unhandled re-entrancy, causing potentially incorrect outcomes.

Also note that I was extremely careful to (almost) exactly match the pure Python implementation.  Not only did this guarantee equivalent behavior, but it simplified the porting effort.  I'm not opposed to deviating from the pure Python implementation as long as the behavior remains exactly the same.  So if you want to change the behavior of OrderedDict you must be sure to make the equivalent change in the pure Python implementation (with the associated backward-compatibility constraints).  Thanks again for working on this though.
msg253122 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-17 06:00
Thank you for your review Eric.

As for using Py_TYPE(self) instead of the __class__ attribute in #3, this is consistent with the rest of Python core and stdlib. All C implementations use Py_TYPE(self) for repr and pickling, even if Python implementations can use __class__.

>>> class S(set): __class__ = str
... 
>>> s = S()
>>> s.__class__
<class 'str'>
>>> s
S()
>>> s.__reduce_ex__(2)
(<class '__main__.S'>, ([],), {})
>>> s+''
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'S' and 'str'

Note that you can't just set s.__class__ = str, this is forbidden (issue24912). You should set __class__ in class definition or make it a property, and this doesn't have affect object's behavior, the only visible effect is the value of the __class__ attribute itself.

One possible argument for Py_TYPE(self) (besides simplicity and historical reasons) is that it is more reliable. It doesn't cause executing arbitrary Python code and therefore is thread-safe and reentrant. It returns the true type of the object, that can be important for debugging.

We should not care about exactly matching Python implementation, but rather about consistency with the rest of Python. If such type of mismatching is considered a bug, Python is full of such bugs.

About #5, be sure that the new code is exact semantic equivalence to the old code besides copying the dict that is not needed now. I just dropped an iteration on always empty dict and related code.

I don't see re-entrancy problem in #7. Could you please provide an example?
msg253133 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-17 16:01
Regarding Py_TYPE(od) vs. od.__class__, there is a difference for subclasses, as you demonstrated in your example. [1]  Thanks for explaining your rationale.  I now understand your argument about using PyTYPE() for repr and pickle in C types.  I still have concerns, though, regarding parity between the two OrderedDict implementations.

The key difference is that we're talking about an after-the-fact C port of an existing pure Python implementation.  The two implementations should behave identically in nearly every case.  The only place I would expect a deviation to not matter is for anything where Python-as-a-whole does not guarantee behavior.  However there are *very* few of those when all is said and done.  Any other variation should not be made casually and only if we are willing to accept that there may be code out there that relies on the pure Python behavior which the C implementation will break.

So like I said before, as a rule I'm absolutely okay with changing the behavior as long as the pure Python implementation is changed to match and OrderedDict remains backward-compatible (and the change is meaningful, e.g. efficiency, consistency).  Otherwise my concerns remain and we have to have sufficient justification for the change.

It may be worth taking this to python-dev to get a clearer consensus on both "Py_TYPE(obj) vs. obj.__class__", as well as about parity between dual pure-Python/C implementations in the stdlib, regardless of the outcome of this issue.  Both are points about which we should be consistent throughout Python.  The type() vs. __class__ question may deserve an entry in the language reference and both may deserve a PEP.  

-----

For this particular case, I think we should still aim for compatibility with the pure Python implementation.  To that effect, we could use Py_TYPE(od) only if PyODict_CheckExact() returns true (as a fast path) and use od.__class__ otherwise.  That fast path would be safe for the C implementation since code can't change OrderedDict().__class__ (due to #24912).

That particular difference in the implementations (i.e. you *can* change od.__class__ for the pure Python one) is an acceptable compatibility break since it's unlikely anyone is changing od.__class__ *and* if they are then they can just switch to a simple subclass that wraps OrderedDict:

    # before:
    from collections import OrderedDict
    od = OrderedDict()
    od.__class__ = SomethingElse

    # after:
    import collections
    class OrderedDict(collections.OrderedDict):
        pass
    od = OrderedDict()
    od.__class__ = SomethingElse

If we *do* continue supporting "type(od) != od.__class__" in repr then I'd say why bother with a fast path for PyOdict_CheckExact().  That sort of efficiency isn't necessary for repr.  If we stop supporting a differing od.__class__ then I'm fine simply using Py_TYPE() throughout.
    
Likewise, if this is not a case we want to support then we must accept that we may break some code out there, however unlikely that is.  In that case perhaps we could be more clear in the documentation that OrderedDict().__class__ should not be changed, though such an addition to the OrderedDict docs might just be clutter.  A general FAQ or other broader doc entry about not assigning to obj.__class__ for stdlib types might be more appropriate.  But that is where clarification from python-dev would help.


[1] There is also a difference between type(obj) and obj.__class__ in the case of proxies (e.g. see #16251), but that is less of an issue here.
msg253134 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-17 16:19
Regarding #5, you're right about OrderedDict().__dict__ being empty for the C implementation.  (Nice observation!)  So I'm okay with ripping all that code out of odict_reduce().  Since we're still accessing od.__dict__ through _PyObject_GetAttrId() that should not impact subclassing.
msg253135 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-17 16:45
Regarding #7, I see what you did now.  That looks fine to me.
msg253137 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-17 17:21
> Regarding Py_TYPE(od) vs. od.__class__, there is a difference for
> subclasses, as you demonstrated in your example. [1]  Thanks for explaining
> your rationale.  I now understand your argument about using PyTYPE() for
> repr and pickle in C types.  I still have concerns, though, regarding
> parity between the two OrderedDict implementations.
> 
> The key difference is that we're talking about an after-the-fact C port of
> an existing pure Python implementation.

There is no a difference. io, pickle, ElementTree, bz2, virtually all 
accelerator classes was created as replacements of pure Python 
implementations. All C implementations use Py_TYPE(self) for repr() and 
pickling. I think this deviation is common and acceptable.

Backward compatibility related to __class__ assignment was already broken in C 
implementation. In 3.4 following code works:

>>> from collections import *
>>> class foo(OrderedDict):
...     def bark(self): return "spam"
... 
>>> class bar(OrderedDict):
...     pass
... 
>>> od = bar()
>>> od.__class__ = foo
>>> od.bark()
'spam'

In 3.5 it doesn't.

> That particular difference in the implementations (i.e. you *can* change
> od.__class__ for the pure Python one) is an acceptable compatibility break
> since it's unlikely anyone is changing od.__class__ *and* if they are then
> they can just switch to a simple subclass that wraps OrderedDict:
> 
>     # before:
>     from collections import OrderedDict
>     od = OrderedDict()
>     od.__class__ = SomethingElse
> 
>     # after:
>     import collections
>     class OrderedDict(collections.OrderedDict):
>         pass
>     od = OrderedDict()
>     od.__class__ = SomethingElse

No, this assignment is forbidden (due to #24912). You can't set __class_ for 
an instance of a subclass of non-heap type.

> It may be worth taking this to python-dev to get a clearer consensus on both
> "Py_TYPE(obj) vs. obj.__class__", as well as about parity between dual
> pure-Python/C implementations in the stdlib, regardless of the outcome of
> this issue.  Both are points about which we should be consistent throughout
> Python.  The type() vs. __class__ question may deserve an entry in the
> language reference and both may deserve a PEP.

Could you please raise a discussion on Python-Dev? You will formulate the 
problem better.
msg253139 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-17 19:22
Updated patch addresses Eric's comments. Changes related to #3 are reverted. We will return to this after discussing on Python-Dev.
msg253140 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-17 21:06
new patch LGTM
msg253141 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-17 21:10
> Backward compatibility related to __class__ assignment was already broken in C
> implementation. In 3.4 following code works:
[snip]
> In 3.5 it doesn't.

Depending on what feedback we get from python-dev, that may need to be
fixed.  I'd be inclined to not worry about it. :)

> No, this assignment is forbidden (due to #24912). You can't set __class_ for
> an instance of a subclass of non-heap type.

Ah, I see.  So the solution to that issue has *forced* a compatibility break.

> Could you please raise a discussion on Python-Dev? You will formulate the
> problem better.

I will.
msg253142 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-17 22:21
Posted to python-dev:

https://mail.python.org/pipermail/python-dev/2015-October/141953.html

https://mail.python.org/pipermail/python-dev/2015-October/141954.html
msg253144 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2015-10-18 06:55
New changeset b6e33798f82a by Serhiy Storchaka in branch '3.5':
Issue #25410: Cleaned up and fixed minor bugs in C implementation of OrderedDict.
https://hg.python.org/cpython/rev/b6e33798f82a

New changeset 741ef17e9b86 by Serhiy Storchaka in branch 'default':
Issue #25410: Cleaned up and fixed minor bugs in C implementation of OrderedDict.
https://hg.python.org/cpython/rev/741ef17e9b86
msg253149 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-18 08:24
Here is a patch that makes both implementations to use type(self) instead of self.__class__ in __repr__(), __reduce__() and copy().

There is a difference between current implementations. Python implementation uses self.__class__ in copy(), C implementation uses type(self).
msg253217 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-20 13:41
Seems there is a leak in _odict_add_new_node() when PyObject_Hash(key) fails. Here is a fix.
msg253218 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-20 14:48
both patches* LGTM

* odict_type.patch and odict_add_new_node_leak.patch
msg253219 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-20 14:50
And thanks again, Serhiy, for taking the time on this. :)
msg253221 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2015-10-20 15:24
New changeset 93f948120773 by Serhiy Storchaka in branch '3.5':
Issue #25410: Fixed a memory leak in OrderedDict in the case when key's hash
https://hg.python.org/cpython/rev/93f948120773

New changeset c3cec0f77eff by Serhiy Storchaka in branch 'default':
Issue #25410: Fixed a memory leak in OrderedDict in the case when key's hash
https://hg.python.org/cpython/rev/c3cec0f77eff
msg253227 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-20 16:03
Thank you for your reviews and discussions (and for your appreciated C acceleration of OrderedDict of course) Eric. I just want to make the code a little cleaner and reliable.

As for odict_type.patch, I would prefer to commit only C part of the patch and left Python implementation unchanged. There few not very strong arguments for __class__ against type() in Python code.

1. Calling type() needs to make globals and builtins lookup. This is more than 2 times slower than access the __class__ attribute. Not critical for __repr__(), __reduce__() and copy().

2. If the code is invoked at shutdown after destroying the builtins module, type can be None. We already had issues with this in the past. In current Python such situation is almost impossible nevertheless, due to different defensive techniques.
msg253244 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-10-20 19:25
Since the python-dev discussion about __class__, leaving the Python implementation alone is fine with me.
msg253341 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2015-10-22 17:20
New changeset a42c0c1c5133 by Serhiy Storchaka in branch '3.5':
Issue #25410: C implementation of OrderedDict now uses type(self) instead of
https://hg.python.org/cpython/rev/a42c0c1c5133

New changeset 10b965d59b49 by Serhiy Storchaka in branch 'default':
Issue #25410: C implementation of OrderedDict now uses type(self) instead of
https://hg.python.org/cpython/rev/10b965d59b49
msg253346 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-22 18:48
Thanks Eric.

A comment in PyODict_SetItem suggests to revert setting the value on the dict if adding new node failed. Following patch implements this suggestion. After committing the patch in issue25462 PyDict_DelItem can be replaced with _PyDict_DelItem_KnownHash and it will be always successful.
msg253347 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-22 18:53
Following patch makes the code for the iterating a little simpler by merging common code.
msg253349 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-10-22 19:14
In very rare circumstances it is possible that after a series of modifications using raw dict methods, ma_keys becomes to point to the same address, but allocated keys array has different size. But the guard in _odict_get_index uses only the address as resize sentinel. This can cause segmentation error if found key index is larger than the size of od_fast_nodes. Following patch adds the value of keys->dk_size as a sentinel.
msg254072 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-11-04 21:19
Could you please make a review of last three patches Eric?
msg254075 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-11-04 23:04
I will review those patches soon.
msg254152 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-11-06 00:02
All 3 patches look fine to me.

In "odict_resize_sentinel.patch", it would be nice if you could accomplish that with a single sentinel.  However, fixing the bug is more critical.
msg254180 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2015-11-06 10:01
New changeset 45ce4c6b4f36 by Serhiy Storchaka in branch '3.5':
Issue #25410: Made testing that od_fast_nodes and dk_entries are in sync more
https://hg.python.org/cpython/rev/45ce4c6b4f36

New changeset c16af48153a4 by Serhiy Storchaka in branch 'default':
Issue #25410: Made testing that od_fast_nodes and dk_entries are in sync more
https://hg.python.org/cpython/rev/c16af48153a4
msg254181 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-11-06 10:10
Thank you for your review Eric.

I made error in commit messages, that is why they are non shown here. odict_revert_setting_on_error.patch and odict_iternext_simpler.patch were committed in 1594c23d8c2f and ad44d551c13c.

od_resize_sentinel2 in odict_resize_sentinel.patch was renamed to od_fast_nodes_size. Now I see that this is not enough. It is possible that ma_keys is located on the same place, has the same size, but has different layout for keys with matched hashes. I'm trying to write more reliable checks.
msg256922 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-12-23 16:16
Following code prints X([(1, 1), (3, 3)]) on 3.4 and X([(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]) on 3.5+.

from collections import OrderedDict
class X(OrderedDict):
    def __iter__(self):
        for k in OrderedDict.__iter__(self):
            if k % 2:
                yield k

od = X((i, i) for i in range(5))
print(od.copy())
msg256923 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-12-23 16:22
And even simpler example: list(od.keys()) is [1, 3] in 3.4 and [0, 1, 2, 3, 4] in 3.5.
msg256987 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-12-25 12:21
Proposed patch makes OrderedDict.copy() more consistent between implementations.
msg313971 - (view) Author: Cheryl Sabella (cheryl.sabella) * (Python committer) Date: 2018-03-16 19:45
Is this issue still relevant?
msg329220 - (view) Author: Dan Snider (bup) * Date: 2018-11-04 09:11
It might be more appropriate to start a new issue for this, but I'll leave that decision to somehow who would know for sure. Anyway, basically the entire dict/PyDictObject api functions do not appear work at all with OrderedDict. Or rather, OrderedDict doesn't seem to be able to recognize the changes the dict api makes to an object. This is present in both 3.6.0 and 3.7.0 by the way.

	from operator import setitem
	from collections import OrderedDict
	from pprint import pprint
	class thing:
		def __init__(self):
			ns = OrderedDict(a='od.__init__')
			vars(__class__)['__dict__'].__set__(self, ns)
			dict.__setitem__(ns, 'b', 'dict.__setitem__')
			self.c = 'PyObject_SetAttr'
			OrderedDict.__setitem__(ns, 'd', 'od.__setitem__')
			ns.update(e='od.update')
			object.__setattr__(self, 'f', 'PyObject_GenericSetAttr')
			setattr(self, 'f', 'PyObject_SetAttr')
			setitem(ns, 'g', 'od.__setitem__')
			dict.update(ns, h='dict.update')
			dict.setdefault(ns, 'i', 'i')
	self    = thing()
	ns      = self.__dict__
	real_ns = {**ns}
	missing = {k: ns[k] for k in real_ns.keys() - ns.keys()}
	pprint(ns)
	pprint(missing, width=len(f'{missing}')-1)
	print(f'"b" in {ns.keys()} == {"b" in ns.keys()}')
	print(f'"b" in {*ns.keys(),} == {"b" in [*ns.keys()]}')
	del ns['a']
	del ns['b']
	print(f"ns.get('c', KeyError('c')) == {ns.get('c', KeyError('c'))}")
	print(f"ns.pop('c', KeyError('c')) == {ns.pop('c', KeyError('c'))!r}")
	ns.get('i')
	ns.pop('i')

Maybe it's considered undefined behavior for a subclass to use 
 a method of one of its bases which it has overriden. That's fair enough, but as this example demonstrates, the silence and arbitrariness of the errors is a real problem when OrderedDict is used as a namespace, since it's a complete coin toss on whether one of the many internal API function will set an attribute or name via PyDict_SetItem or PyObject_SetItem. Only the former can invoke the methods OrderedDict overrides and there isn't any easy-to-find on the subject as far as I know.
msg329260 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2018-11-04 21:31
> Maybe it's considered undefined behavior for a subclass to use 
> a method of one of its bases which it has overriden.

In general, it's true that if OrderedDict is a subclass of dict, then it would have no defense against someone making a direct call to the dict base class.  Such a call should be expected to violate the OrderedDicts invariants.

> it's a complete coin toss on whether one of the many internal
> API function will set an attribute or name via PyDict_SetItem 
> or PyObject_SetItem. 

Not really.  The CPython source is supposed to only call PyDict_SetItem when the target is known to be an exact dictionary.  If you find a case where that isn't true, please file a bug report and we'll fix it.

> It might be more appropriate to start a new issue for this, but I'll > leave that decision to somehow who would know for sure.

No need.  We've known about this sort of problem for years.  See https://bugs.python.org/issue10977 for example.  There isn't really much we could do about it without causing other issues that would be worse.

FWIW, this doesn't seem to be a problem in practice.  Further, OrderedDict is expected to become less relevant now that regular dicts are order preserving.
History
Date User Action Args
2022-04-11 14:58:22adminsetgithub: 69596
2018-11-04 21:31:17rhettingersetmessages: + msg329260
2018-11-04 09:11:38bupsetnosy: + bup
messages: + msg329220
2018-03-16 19:45:01cheryl.sabellasetnosy: + cheryl.sabella
messages: + msg313971
2015-12-25 12:21:04serhiy.storchakasetfiles: + odict_copy_iter.patch

messages: + msg256987
stage: needs patch -> patch review
2015-12-23 16:22:19serhiy.storchakasetmessages: + msg256923
2015-12-23 16:16:15serhiy.storchakasetmessages: + msg256922
2015-11-06 10:10:40serhiy.storchakasetmessages: + msg254181
stage: commit review -> needs patch
2015-11-06 10:01:06python-devsetmessages: + msg254180
2015-11-06 00:02:30eric.snowsetstage: patch review -> commit review
2015-11-06 00:02:04eric.snowsetmessages: + msg254152
2015-11-04 23:04:17eric.snowsetmessages: + msg254075
2015-11-04 21:19:33serhiy.storchakasetmessages: + msg254072
2015-10-22 19:14:09serhiy.storchakasetfiles: + odict_resize_sentinel.patch

messages: + msg253349
2015-10-22 18:53:14serhiy.storchakasetfiles: + odict_iternext_simpler.patch

messages: + msg253347
2015-10-22 18:48:56serhiy.storchakasetfiles: + odict_revert_setting_on_error.patch

messages: + msg253346
stage: commit review -> patch review
2015-10-22 17:20:04python-devsetmessages: + msg253341
2015-10-20 19:25:13eric.snowsetmessages: + msg253244
stage: patch review -> commit review
2015-10-20 16:03:47serhiy.storchakasetmessages: + msg253227
2015-10-20 15:24:07python-devsetmessages: + msg253221
2015-10-20 14:50:46eric.snowsetmessages: + msg253219
2015-10-20 14:48:33eric.snowsetmessages: + msg253218
2015-10-20 13:41:52serhiy.storchakasetfiles: + odict_add_new_node_leak.patch

messages: + msg253217
stage: commit review -> patch review
2015-10-18 08:24:04serhiy.storchakasetfiles: + odict_type.patch

messages: + msg253149
2015-10-18 06:55:32python-devsetnosy: + python-dev
messages: + msg253144
2015-10-17 22:21:32eric.snowsetmessages: + msg253142
stage: patch review -> commit review
2015-10-17 21:10:07eric.snowsetmessages: + msg253141
2015-10-17 21:06:30eric.snowsetmessages: + msg253140
2015-10-17 19:23:19serhiy.storchakasetfiles: + odict_cleanup_2.patch
2015-10-17 19:22:54serhiy.storchakasetmessages: + msg253139
2015-10-17 17:21:35serhiy.storchakasetmessages: + msg253137
2015-10-17 16:45:09eric.snowsetmessages: + msg253135
2015-10-17 16:19:10eric.snowsetmessages: + msg253134
2015-10-17 16:01:20eric.snowsetmessages: + msg253133
2015-10-17 06:00:58serhiy.storchakasetmessages: + msg253122
2015-10-17 01:06:59eric.snowsetmessages: + msg253110
2015-10-15 14:59:11rhettingersetmessages: + msg253051
2015-10-15 08:05:08serhiy.storchakacreate