[ 
https://issues.apache.org/jira/browse/GROOVY-12669?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18137440#comment-18137440
 ] 

ASF GitHub Bot commented on GROOVY-12669:
-----------------------------------------

codeconsole opened a new pull request, #3190:
URL: https://github.com/apache/groovy/pull/3190

   A memoized closure forwards calls to the closure it wraps, which is held in 
its `closure` field. `rehydrate` and `dehydrate` were inherited from `Closure`, 
so they replaced only the wrapper's own owner, delegate and this object. The 
wrapped closure kept the original ones:
   
   ```groovy
   class O { def foo() { 'o' } }
   class P { def foo() { 'p' } }
   def c = new O().with { o -> { x -> foo() + x } }
   c.rehydrate(null, new P(), new P()).memoize()(1)   // 'p1'
   c.memoize().rehydrate(null, new P(), new P())(1)   // 'o1'
   ```
   
   The same happens for `memoizeAtMost`, `memoizeAtLeast` and `memoizeBetween`, 
in 4.0.33, 5.0.8 and 6.0.0 too. A dehydrated `memoize()` closure could not be 
serialized, because the wrapped closure still referenced the owner.
   
   `MemoizeFunction` now overrides `dehydrate` and `rehydrate`. They dehydrate 
or rehydrate the wrapped closure and memoize the copy again. The copy gets an 
empty cache, since the cached values were computed against the old owner. To 
build that cache the same way, the memoized closure keeps the factory its cache 
came from: `Closure.memoize*` pass a serializable factory to new 
`Memoize.buildMemoizeFunction(Supplier, Closure)` and 
`Memoize.buildSoftReferenceMemoizeFunction(int, Supplier, Closure)` overloads. 
No existing signature changes. The soft-reference variant also keeps its 
protected cache size and gives the copy fresh protection storage and a fresh 
reference queue. The existing overloads that take a cache instance cannot build 
another one, so their copy shares the supplied cache; their Javadoc says so.
   
   This is the memoize counterpart of #3186 (GROOVY-12665), which does the same 
for curried, writable, composed and trampolined closures.
   
   ## Tests
   
   These fail without the change:
   - in `AbstractMemoizeTestCase`, for all four variants: 
`testRehydrateCallsTheRehydratedClosure`, `testRehydrateUsesTheNewDelegate` and 
`testDehydrateClearsTheMemoizedClosure`
   - in `MemoizeTest`: `testRehydratedCopyKeepsAnUnlimitedCache`, 
`testDehydratedCopySerializesAndRehydrates` and 
`testCopyOfClosureMemoizedWithSuppliedCacheSharesTheCache`
   - `MemoizeAtMostTest.testRehydratedCopyKeepsTheMaximumCacheSize`
   - `MemoizeBetweenTest.testRehydratedCopyKeepsTheCacheSizes`
   - `MemoizeAtLeastTest.testRehydratedCopyKeepsTheProtectedCacheSize` and 
`testCopyOfClosureMemoizedWithSuppliedCacheSharesTheCache`
   
   `org.codehaus.groovy.runtime.memoize.*`, `groovy.lang.*Closure*`, 
`groovy.*Closure*`, `groovy.*Memoize*` and `MemoizedASTTransformationTest` 
pass. The Checkstyle and CodeNarc gates pass.
   




> Rehydrating or dehydrating a memoized closure keeps calling the original 
> closure with its old owner and delegate
> ----------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12669
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12669
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 4.0.33, 5.0.8, 7.0.0-alpha-1, 6.0.0
>            Reporter: Scott Murphy Heiberg
>            Priority: Major
>
> {code:groovy}
> class O { def foo() { 'o' } }
> class P { def foo() { 'p' } }
> def c = new O().with { o -> { x -> foo() + x } }
> assert c.rehydrate(null, new P(), new P()).memoize()(1) == 'p1'
> println c.memoize().rehydrate(null, new P(), new P())(1)        // prints o1
> println c.memoizeAtMost(5).rehydrate(null, new P(), new P())(1) // prints o1
> new ObjectOutputStream(new 
> ByteArrayOutputStream()).writeObject(c.memoize().dehydrate())
> {code}
> *Actual:* a rehydrated copy of {{memoize()}}, {{memoizeAtMost}}, 
> {{memoizeAtLeast}} or {{memoizeBetween}} prints {{o1}}. Serializing the 
> dehydrated memoized closure fails with {{java.io.NotSerializableException: 
> O}}.
> *Expected:* {{p1}}, as for rehydrate followed by memoize. The dehydrated 
> memoized closure serializes.
> Cause: the memoized closure forwards calls to the closure it wraps, held in a 
> separate field. The inherited {{rehydrate}} and {{dehydrate}} replace only 
> the wrapper's own owner, delegate and this object, so the wrapped closure 
> keeps the old ones. The copy also shares the original's cache. Related: 
> GROOVY-12665 (the same for curried, writable, composed and trampolined 
> closures).
> Verified on 4.0.33, 5.0.8, 6.0.0 and master.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to