[
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)