anton-vinogradov commented on code in PR #13447:
URL: https://github.com/apache/ignite/pull/13447#discussion_r3753940822


##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryInfo.java:
##########
@@ -64,58 +68,70 @@ public class GridCacheEntryInfo implements 
SelfMarshallingMessage, CacheIdAware
     /** Deleted flag. */
     private boolean deleted;
 
-    /** {@inheritDoc} */
-    @Override public int cacheId() {
-        return cacheId;
-    }
-
     /**
-     * @param cacheId Cache ID.
+     * Empty constructor for serialization purposes.
+     * see {@link #expireTimeDelta}.
      */
-    public void cacheId(int cacheId) {
+    public GridCacheEntryInfo() {
+        initTime = U.currentTimeMillis();

Review Comment:
   Yes, always, and not setting it is a live defect right now.
   
   `expireTime()` starts with `assert initTime > 0`, before the check that 
would return 0. For a cache with no expiry policy the constructor skips the `if 
(expireTime != 0)` branch, `initTime` stays 0 and the assert throws. It is 
thrown inside a future listener and swallowed there, so the near lock future 
never completes:
   
   ```
   (err) Failed to notify listener: GridEmbeddedFuture$1@7be5697c
   java.lang.AssertionError
        at GridCacheEntryInfo.expireTime(GridCacheEntryInfo.java:131)
        at GridNearCacheEntry.initializeFromDht(GridNearCacheEntry.java:156)
        at GridNearCacheAdapter.entryEx(GridNearCacheAdapter.java:147)
        at GridNearLockFuture$1.apply(GridNearLockFuture.java:1265)
   ```
   
   `GridCacheNearPrimarySyncSelfTest` on `f95cc504` hangs there for 18 minutes 
with a long running transaction until killed. 
`GridNearCacheAdapter.entryEx(key, topVer)` calls `initializeFromDht` on every 
near entry lookup, so any near cache without an expiry policy hits it.
   
   Setting `initTime` unconditionally costs one volatile read, and the same 
test then finishes in 0.25 s with no assertion errors in the log.



##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryInfo.java:
##########
@@ -64,58 +68,70 @@ public class GridCacheEntryInfo implements 
SelfMarshallingMessage, CacheIdAware
     /** Deleted flag. */
     private boolean deleted;
 
-    /** {@inheritDoc} */
-    @Override public int cacheId() {
-        return cacheId;
-    }
-
     /**
-     * @param cacheId Cache ID.
+     * Empty constructor for serialization purposes.
+     * see {@link #expireTimeDelta}.
      */
-    public void cacheId(int cacheId) {
+    public GridCacheEntryInfo() {
+        initTime = U.currentTimeMillis();
+    }
+
+    /** */
+    public GridCacheEntryInfo(int cacheId, KeyCacheObject key, @Nullable 
CacheObject val, GridCacheVersion ver, long expireTime, long ttl) {
+        assert expireTime >= 0;
+
+        if (expireTime != 0) {
+            initTime = U.currentTimeMillis();
+
+            expireTimeDelta = expireTime - initTime;
+
+            // In theory, here we can get the thread paused causing a negative 
delta value. Possible negative values
+            // shouldn't be treated as disabled expiration. Correct behavior 
is expired timeout.
+            if (expireTimeDelta < 0)
+                expireTimeDelta = 0;
+        }
+
         this.cacheId = cacheId;
+        this.key = key;
+        this.val = val;
+        this.ver = ver;
+        this.ttl = ttl;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int cacheId() {
+        return cacheId;
     }
 
     /**
      * @param key Entry key.
      */
-    public void key(KeyCacheObject key) {
+    public void key(@Nullable KeyCacheObject key) {
         this.key = key;
     }
 
     /**
      * @return Entry key.
      */
-    public KeyCacheObject key() {
+    @Nullable public KeyCacheObject key() {
         return key;
     }
 
     /**
      * @return Entry value.
      */
-    public CacheObject value() {
+    public @Nullable CacheObject value() {
         return val;
     }
 
     /**
-     * @param val Entry value.
-     */
-    public void value(CacheObject val) {
-        this.val = val;
-    }
-
-    /**
-     * @return Expire time.
+     * @return Expire time >= 0. 0 means no expiration is set.
      */
     public long expireTime() {
-        return expireTime;
-    }
+        assert initTime > 0;
+        assert expireTimeDelta >= -1L;
 
-    /**
-     * @param expireTime Expiration time.
-     */
-    public void expireTime(long expireTime) {
-        this.expireTime = expireTime;
+        return expireTimeDelta == -1L ? 0L : initTime + expireTimeDelta;

Review Comment:
   Two separate ones.
   
   **Overflow.** `initTime + expireTimeDelta` has no guard. The removed 
`selfUnmarshal` had it explicitly:
   
   ```java
   expireTime = remaining < 0 ? 0 : U.currentTimeMillis() + remaining;
   
   // Account for overflow.
   if (expireTime < 0)
       expireTime = 0;
   ```
   
   It kept `expireTime()` non negative, which 
`GridDhtPartitionDemander.preloadEntry` relies on: `assert row.expireTime() >= 
0`. Asserts are off in production, so nothing enforces it there now.
   
   **Only `-1` reads as "no expiration".** The removed `selfUnmarshal` read the 
whole negative range that way, `remaining < 0 ? 0 : ...`, where 0 is 
`CU.EXPIRE_TIME_ETERNAL`. The new getter compares with exactly `-1L`, so any 
other negative value arriving from the wire becomes a time in the past and the 
entry gets cleaned, where master kept it as eternal. A correct sender does not 
produce such a value, but of the two readings the one that keeps the data is 
the safer default, and it also makes `assert expireTimeDelta >= -1L` 
unnecessary.



##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryInfo.java:
##########
@@ -190,30 +192,6 @@ public int marshalledSize(CacheObjectContext ctx) throws 
IgniteCheckedException
         return SIZE_OVERHEAD + size;
     }
 
-    // TODO IGNITE-28920: the rebase still runs inside the message; move it to 
the code filling and reading the entry.
-    /** {@inheritDoc} */
-    @Override public void selfMarshal() {
-        if (expireTime == 0)
-            expireTime = -1;
-        else {
-            expireTime -= U.currentTimeMillis();
-
-            if (expireTime < 0)
-                expireTime = 0;
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override public void selfUnmarshal() {
-        long remaining = expireTime;
-
-        expireTime = remaining < 0 ? 0 : U.currentTimeMillis() + remaining;
-
-        // Account for overflow.
-        if (expireTime < 0)
-            expireTime = 0;
-    }
-
     /** {@inheritDoc} */
     @Override public String toString() {

Review Comment:
   Agreed, nothing is missed for what this constant is used for, but the 
constant is muddled and worth a look.
   
   `initTime` is not transferred, and the wire still carries exactly two longs, 
`ttl` and `expireTimeDelta`, so for a marshalled size the count is unchanged.
   
   The muddle is that the terms are Java object sizes, `3 * 8 /* reference */`, 
while both users of `marshalledSize()` treat the result as bytes on the wire: 
`GridDhtPartitionSupplyMessage.addEntry0` cuts supply batches by it against 
`rebalanceBatchSize`, and `GridDhtPartitionDemander` feeds it into 
`onRebalanceBatchReceived`, the rebalance bytes metric. The real fixed part is 
11 bytes measured on a minimal message and around 20 with a realistic cache id 
and version, against 76 in the constant, so for small entries batches are 
several times smaller than configured and the metric overstates the traffic by 
the same factor.
   
   Pre-existing, not this PR, but if the field list is being revisited anyway 
it is the moment to decide what the constant estimates.



##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java:
##########
@@ -396,23 +396,14 @@ protected GridDhtLocalPartition localPartition() {
 
         try {
             if (!obsolete()) {
-                info = new GridCacheEntryInfo();
-
-                info.key(key);
-                info.cacheId(cctx.cacheId());
-
                 long expireTime = expireTimeExtras();
 
-                boolean expired = expireTime != 0 && expireTime <= 
U.currentTimeMillis();
+                CacheObject val0 = expireTime == 0 || expireTime > 
U.currentTimeMillis() ? val : null;

Review Comment:
   One concrete reason for making it once: right now the decision is split 
across two clock reads.
   
   Here `expireTime > U.currentTimeMillis()` decides whether the value goes, 
and then the constructor reads the clock again for `expireTime - 
U.currentTimeMillis()`. If the two land on different ticks, the entry can be 
sent with its value while its remaining time is clamped to 0, that is "expires 
right now". The receiver stores a value that is dead on arrival.
   
   Harmless today, but it is exactly the kind of thing that follows from 
deciding twice.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to