groovy
  1. groovy
  2. GROOVY-5151

Provide methods to specify owner, delegate and thisObject of closures

    Details

    • Type: Improvement Improvement
    • Status: Closed Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.8.4
    • Fix Version/s: 1.8.5, 2.0-beta-2
    • Component/s: None
    • Labels:
      None
    • Number of attachments :
      1

      Description

      See the discussion at http://groovy.329449.n5.nabble.com/Serializing-Closures-td5015137.html

      Closures are serializable, but the "owner", "delegate" and "thisObject" objects may not be serializable. The idea is to provide a method which returns a copy of the current closure with the above fields nullified.

      A "rehydrate" method should be provided to offer the inverse operation.

        Activity

        Hide
        Cédric Champeau added a comment -

        Comment copied from mailing list:

        There is something interesting to discuss here. I have an implementation of dehydrate/rehydrate for closures which allows setting null the (owner,delegate,thisObject) and rehydrate a closure by specifying those in a single method.

        This works even in case of closures referring another closures, like in this example:

        class Controller { // not Serializable
            def action = { 'Hello' }
            def action2 = { action() } // call to other closure
        }
        def ctrl = new Controller()
        def a2 = ctrl.action2.dehydrate()
        def bos = new ByteArrayOutputStream()
        bos.withObjectOutputStream {
            it << a2
        }
        byte[] arr = bos.toByteArray()
        def rehyd
        new ByteArrayInputStream(arr).withObjectInputStream(this.class.classLoader) {
            it.eachObject { o -> rehyd = o }
        }
        ctrl = new Controller() // assert new instance
        rehyd = rehyd.rehydrate(ctrl, ctrl, ctrl)
        assert rehyd() == 'Hello'
        

        Now, the next example won't work:

        class X {}
        def x = new X()
        def cl = {x}
        def dehyd = cl.dehydrate()
        def bos = new ByteArrayOutputStream()
        bos.withObjectOutputStream {
            it << dehyd
        }
        

        It will fail because it references an outer variable. Internally, the compiler generates a field for the closure class, referencing class X. This class is not serializable, so the field serialization of the closure will fail. As a workaround, one could write this:

        class X {}
        def x = new X()
        def cl = {x}
        def dehyd = cl.dehydrate()
        dehyd.@x = null
        def bos = new ByteArrayOutputStream()
        bos.withObjectOutputStream {
            it << dehyd
        }
        

        But apart from not being user friendly, it beats the concept of "dehydrate" which is to allow closure serialization. We have several possibilities here:

        1. make dehydrate iterate on the closure properties, and if a property is not serializable, nullify it

        • rehydrate would then take an optional parameter, a map, which allows setting the "missing" properties after deserialization
        • possibly, if a closure has another closure as a field member, we could recursively dehydrate them, but rehydrating would be a little more complex process
          2. make dehydrate throw an exception in that case

        I don't really like the idea of a method which wants to make things serializable to fail with an exception if a non serializable member is found (why do this with owner, ... then ?). But as I did not have the need for closure serialization like you did, perhaps you could give us your opinion on that ?

        Show
        Cédric Champeau added a comment - Comment copied from mailing list: There is something interesting to discuss here. I have an implementation of dehydrate/rehydrate for closures which allows setting null the (owner,delegate,thisObject) and rehydrate a closure by specifying those in a single method. This works even in case of closures referring another closures, like in this example: class Controller { // not Serializable def action = { 'Hello' } def action2 = { action() } // call to other closure } def ctrl = new Controller() def a2 = ctrl.action2.dehydrate() def bos = new ByteArrayOutputStream() bos.withObjectOutputStream { it << a2 } byte [] arr = bos.toByteArray() def rehyd new ByteArrayInputStream(arr).withObjectInputStream( this .class.classLoader) { it.eachObject { o -> rehyd = o } } ctrl = new Controller() // assert new instance rehyd = rehyd.rehydrate(ctrl, ctrl, ctrl) assert rehyd() == 'Hello' Now, the next example won't work: class X {} def x = new X() def cl = {x} def dehyd = cl.dehydrate() def bos = new ByteArrayOutputStream() bos.withObjectOutputStream { it << dehyd } It will fail because it references an outer variable. Internally, the compiler generates a field for the closure class, referencing class X. This class is not serializable, so the field serialization of the closure will fail. As a workaround, one could write this: class X {} def x = new X() def cl = {x} def dehyd = cl.dehydrate() dehyd.@x = null def bos = new ByteArrayOutputStream() bos.withObjectOutputStream { it << dehyd } But apart from not being user friendly, it beats the concept of "dehydrate" which is to allow closure serialization. We have several possibilities here: 1. make dehydrate iterate on the closure properties, and if a property is not serializable, nullify it rehydrate would then take an optional parameter, a map, which allows setting the "missing" properties after deserialization possibly, if a closure has another closure as a field member, we could recursively dehydrate them, but rehydrating would be a little more complex process 2. make dehydrate throw an exception in that case I don't really like the idea of a method which wants to make things serializable to fail with an exception if a non serializable member is found (why do this with owner, ... then ?). But as I did not have the need for closure serialization like you did, perhaps you could give us your opinion on that ?
        Hide
        Cédric Champeau added a comment -

        Current implementation sets non serializable members to null and offers an optional map parameter when rehydrating.

        Show
        Cédric Champeau added a comment - Current implementation sets non serializable members to null and offers an optional map parameter when rehydrating.
        Hide
        Cédric Champeau added a comment -

        Following the discussion, I'm only committing the "limited" solution which allows to nullify owner, delegate and thisObject but will blow if any other closure field is not serializable.

        Show
        Cédric Champeau added a comment - Following the discussion, I'm only committing the "limited" solution which allows to nullify owner, delegate and thisObject but will blow if any other closure field is not serializable.

          People

          • Assignee:
            Cédric Champeau
            Reporter:
            Cédric Champeau
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: