<p dir="ltr">Is it mgo/txn that is internally unmarahalling onto that?</p>
<p dir="ltr">Let's get that fixed at its heart.</p>
<div class="gmail_quote">On Jun 8, 2016 12:27 PM, "roger peppe" <<a href="mailto:roger.peppe@canonical.com">roger.peppe@canonical.com</a>> wrote:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">The Assert field in mgo/txn.Op is an interface{}, so<br>
when it's marshaled and unmarshaled, the order<br>
can change because unmarshaling unmarshals as bson.M<br>
which does not preserve key order.<br>
<br>
<a href="https://play.golang.org/p/_1ZPl7iMyn" rel="noreferrer" target="_blank">https://play.golang.org/p/_1ZPl7iMyn</a><br>
<br>
On 8 June 2016 at 15:55, Gustavo Niemeyer<br>
<<a href="mailto:gustavo.niemeyer@canonical.com">gustavo.niemeyer@canonical.com</a>> wrote:<br>
> Is it mgo itself that is changing the order internally?<br>
><br>
> It should not do that.<br>
><br>
> On Wed, Jun 8, 2016 at 8:00 AM, roger peppe <<a href="mailto:rogpeppe@gmail.com">rogpeppe@gmail.com</a>> wrote:<br>
>><br>
>> OK, I understand now, I think.<br>
>><br>
>> The underlying problem is that subdocument searches in MongoDB<br>
>> are order-sensitive.<br>
>><br>
>> For example, I just tried this in a mongo shell:<br>
>><br>
>> > db.foo.insert({_id: "one", x: {a: 1, b: 2}})<br>
>> > db.foo.find({x: {a: 1, b: 2}})<br>
>> { "_id" : "one", "x" : { "a" : 1, "b" : 2 } }<br>
>> > db.foo.find({x: {b: 2, a: 1}})<br>
>> ><br>
>><br>
>> The second find doesn't return anything even though it contains<br>
>> the same fields with the same values as the first.<br>
>><br>
>> Urk. I did not know about that. What a gotcha!<br>
>><br>
>> So it *could* technically be OK if the fields in the struct (and<br>
>> any bson.D) are lexically ordered to match the bson Marshaler,<br>
>> but well worth avoiding.<br>
>><br>
>> I think things would be considerably improved if mgo/bson preserved<br>
>> order by default (by using bson.D) when unmarshaling.<br>
>> Then at least you'd know that the assertion you specify<br>
>> is exactly the one that gets executed.<br>
>><br>
>> cheers,<br>
>> rog.<br>
>><br>
>><br>
>><br>
>><br>
>> On 8 June 2016 at 10:42, Menno Smits <<a href="mailto:menno.smits@canonical.com">menno.smits@canonical.com</a>> wrote:<br>
>> ><br>
>> ><br>
>> > On 8 June 2016 at 21:05, Tim Penhey <<a href="mailto:tim.penhey@canonical.com">tim.penhey@canonical.com</a>> wrote:<br>
>> >><br>
>> >> Hi folks,<br>
>> >><br>
>> >> tl;dr: not use structs in transaction asserts<br>
>> >><br>
>> >> ...<br>
>> >><br>
>> >> The solution is to not use a field struct equality, even though it is<br>
>> >> easy<br>
>> >> to write, but to use the dotted field notation to check the embedded<br>
>> >> field<br>
>> >> values.<br>
>> ><br>
>> ><br>
>> ><br>
>> > To give a more concrete example, asserting on a embedded document field<br>
>> > like<br>
>> > this is problematic:<br>
>> ><br>
>> > ops := []txn.Op{{<br>
>> > C: "collection",<br>
>> > Id: ...,<br>
>> > Assert: bson.D{{"some-field", Thing{A: "foo", B: 99}}},<br>
>> > Update: ...<br>
>> > }<br>
>> ><br>
>> > Due to the way mgo works[1], the document the transaction operation is<br>
>> > asserting against may have been written with A and B in reverse order,<br>
>> > or<br>
>> > the Thing struct in the Assert may have A and B swapped by the time it's<br>
>> > used. Either way, the assertion will fail randomly.<br>
>> ><br>
>> > The correct approach is to express the assertion like this:<br>
>> ><br>
>> > ops := []txn.Op{{<br>
>> > C: "collection",<br>
>> > Id: ...,<br>
>> > Assert: bson.D{<br>
>> > {"some-field.A", "foo"},<br>
>> > {"some-field.B", 99},<br>
>> > },<br>
>> > Update: ...<br>
>> > }<br>
>> ><br>
>> > or this:<br>
>> ><br>
>> > ops := []txn.Op{{<br>
>> > C: "collection",<br>
>> > Id: ...,<br>
>> > Assert: bson.M{<br>
>> > "some-field.A": "foo",<br>
>> > "some-field.B": 99,<br>
>> > },<br>
>> > Update: ...<br>
>> > }<br>
>> ><br>
>> >><br>
>> >> Yet another thing to add to the list of things to check when doing<br>
>> >> reviews.<br>
>> ><br>
>> ><br>
>> > I think we can go a bit further and error on attempts to use structs for<br>
>> > comparison in txn.Op asserts in Juju's txn layers in state. Just as we<br>
>> > already do some munging and checking of database operations to ensure<br>
>> > correct multi-model behaviour, we should be able to do this same for<br>
>> > this<br>
>> > issue and prevent it from happening again.<br>
>> ><br>
>> > - Menno<br>
>> ><br>
>> > [1] If transaction operations are loaded and used from the DB (more<br>
>> > likely<br>
>> > under load when multiple runners are acting concurrently), the Insert,<br>
>> > Update and Assert fields are loaded as bson.M (this is what the bson<br>
>> > Unmarshaller does for interface{} typed fields). Once this happens field<br>
>> > ordering is lost.<br>
>> ><br>
>> ><br>
>> ><br>
>> ><br>
>> > --<br>
>> > Juju-dev mailing list<br>
>> > <a href="mailto:Juju-dev@lists.ubuntu.com">Juju-dev@lists.ubuntu.com</a><br>
>> > Modify settings or unsubscribe at:<br>
>> > <a href="https://lists.ubuntu.com/mailman/listinfo/juju-dev" rel="noreferrer" target="_blank">https://lists.ubuntu.com/mailman/listinfo/juju-dev</a><br>
>> ><br>
>><br>
>> --<br>
>> Juju-dev mailing list<br>
>> <a href="mailto:Juju-dev@lists.ubuntu.com">Juju-dev@lists.ubuntu.com</a><br>
>> Modify settings or unsubscribe at:<br>
>> <a href="https://lists.ubuntu.com/mailman/listinfo/juju-dev" rel="noreferrer" target="_blank">https://lists.ubuntu.com/mailman/listinfo/juju-dev</a><br>
><br>
><br>
><br>
><br>
> --<br>
> gustavo @ <a href="http://niemeyer.net" rel="noreferrer" target="_blank">http://niemeyer.net</a><br>
</blockquote></div>