All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.caucho.server.distcache.DistCacheEntry Maven / Gradle / Ivy

/*
 * Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.server.distcache;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

import com.caucho.cloud.topology.TriadOwner;
import com.caucho.server.distcache.DataStore.DataItem;
import com.caucho.server.distcache.LocalDataManager.DataItemLocal;
import com.caucho.server.util.CauchoSystem;
import com.caucho.util.CurrentTime;
import com.caucho.util.HashKey;
import com.caucho.util.Hex;
import com.caucho.util.IoUtil;
import com.caucho.util.L10N;
import com.caucho.vfs.StreamSource;

/**
 * An entry in the cache map
 */
public class DistCacheEntry {
  private static final L10N L = new L10N(DistCacheEntry.class);
  
  private static final Logger log
    = Logger.getLogger(DistCacheEntry.class.getName());
  
  private final CacheStoreManager _cacheService;
  private final HashKey _keyHash;
  private final CacheHandle _cache;
  private final TriadOwner _owner;

  private Object _key;

  private final AtomicBoolean _isReadUpdate = new AtomicBoolean();

  private final AtomicReference _mnodeEntry
    = new AtomicReference(MnodeEntry.NULL);
  
  private final AtomicInteger _loadCount = new AtomicInteger();

  private long _lastLoaderTime;

  DistCacheEntry(CacheStoreManager cacheService,
                 HashKey keyHash,
                 CacheHandle cache,
                 TriadOwner owner)
  {
    _cacheService = cacheService;
    _keyHash = keyHash;
    _cache = cache;
    
    _owner = TriadOwner.getHashOwner(keyHash.getHash());
    
    _mnodeEntry.set(MnodeEntry.createInitialNull(cache.getConfig()));
  }

  /**
   * Returns the key for this entry in the Cache.
   */
  public final Object getKey()
  {
    return _key;
  }
  
  public final void setKey(Object key)
  {
    if (_key == null)
      _key = key;
  }
  
  private LocalDataManager getLocalDataManager()
  {
    return _cacheService.getLocalDataManager();
  }

  /**
   * Returns the keyHash
   */
  public final HashKey getKeyHash()
  {
    return _keyHash;
  }

  /**
   * Returns the owner
   */
  public final TriadOwner getOwner()
  {
    return _owner;
  }
  
  public HashKey getCacheKey()
  {
    return _cache.getCacheKey();
  }
  
  public byte []getCacheKeyHash()
  {
    return _cache.getCacheKeyHash();
  }
  
  public CacheHandle getCache()
  {
    return _cache;
  }
  
  public CacheConfig getConfig()
  {
    return _cache.getConfig();
  }
  
  public CacheEngine getEngine()
  {
    return getConfig().getEngine();
  }

  /**
   * Returns the value section of the entry.
   */
  public final MnodeEntry getMnodeEntry()
  {
    return _mnodeEntry.get();
  }

  /**
   * Returns the object for the given key, checking the backing if necessary.
   * If it is not found, the optional cacheLoader is invoked, if present.
   */
  public Object get()
  {
    long now = CurrentTime.getCurrentTime();

    return get(now);
  }

  /**
   * Returns the object for the given key, checking the backing if necessary.
   * If it is not found, the optional cacheLoader is invoked, if present.
   */
  /*
  public Object getExact(CacheConfig config)
  {
    long now = CurrentTime.getCurrentTime();

    return get(config, now, true);
  }
  */

  /**
   * Returns the object for the given key, checking the backing if necessary
   */
  public MnodeEntry loadMnodeValue()
  {
    long now = CurrentTime.getCurrentTime();

    // server/01o9
    return loadMnodeValue(now, false); // , false);
  }

  /**
   * Gets a cache entry as a stream
   */
  final public StreamSource getValueStream()
  {
    MnodeEntry mnodeValue = getMnodeEntry();

    // server/01o9
    // updateAccessTime();

    return getLocalDataManager().createDataSource(mnodeValue.getValueDataId(),
                                                  mnodeValue.getValueDataTime());
  }

  public long getValueHash(Object value, CacheConfig config)
  {
    if (value == null)
      return 0;

    return _cacheService.calculateValueHash(value, config);
  }
  
  public CacheUpdateWithSource loadCacheStream(long requestVersion,
                                               boolean isValueStream)
  {
    MnodeEntry mnodeEntry = getMnodeEntry();
    
    if (mnodeEntry.getVersion() <= requestVersion) {
      return new CacheUpdateWithSource(mnodeEntry, null, 
                                       mnodeEntry.getLeaseOwner());
    }
    else if (mnodeEntry.isImplicitNull()) {
      return new CacheUpdateWithSource(mnodeEntry, null, 
                                       mnodeEntry.getLeaseOwner());
    }

    StreamSource source = null;
      
    if (isValueStream) {
      long valueDataId = mnodeEntry.getValueDataId();
      long valueDataTime = mnodeEntry.getValueDataTime();
      
      DataStreamSource dataSource
          = getLocalDataManager().createDataSource(valueDataId, valueDataTime);

      if (dataSource != null) {
        source = new StreamSource(dataSource);
      }

      // XXX: updateLease(entryKey, mnodeEntry, leaseOwner);
    }

    return new CacheUpdateWithSource(mnodeEntry, source, mnodeEntry.getLeaseOwner());
  }

  /**
    * Sets a cache entry
    */
   final public void put(Object value)
   {
     long now = CurrentTime.getCurrentTime();
     
     // server/60a0 - on server '4', need to read update from triad
     // MnodeEntry mnodeValue = loadMnodeValue(now, true); // , false);
     
     MnodeEntry mnodeValue = loadLocalMnodeValue();

     put(value, now, mnodeValue, true);
   }

   /**
     * Sets a cache entry
     */
    final public void putInternal(Object value)
    {
      long now = CurrentTime.getCurrentTime();

      // server/60a0 - on server '4', need to read update from triad
      // MnodeEntry mnodeValue = loadMnodeValue(now, true); // , false);
      
      MnodeEntry mnodeValue = loadLocalMnodeValue();

      put(value, now, mnodeValue, false);
    }

    /**
     * Sets the value by an input stream
      */
    public void put(InputStream is)
      throws IOException
    {
      long now = CurrentTime.getCurrentTime();
      long lastAccessTime = now;
      long lastModifiedTime = now;
      
      CacheConfig config = getConfig();
      
      putStream(is,
                config.getAccessedExpireTimeout(),
                config.getModifiedExpireTimeout(), 
                0,
                lastAccessTime,
                lastModifiedTime,
                0,
                false);
    }

  /**
   * Sets the value by an input stream
    */
  public void putLocal(InputStream is)
    throws IOException
  {
    long now = CurrentTime.getCurrentTime();
    long lastAccessTime = now;
    long lastModifiedTime = now;
    
    CacheConfig config = getConfig();
    
    putStream(is, 
              config.getAccessedExpireTimeout(),
              config.getModifiedExpireTimeout(), 
              0,
              lastAccessTime,
              lastModifiedTime,
              0,
              true);
  }

  /**
   * Sets the value by an input stream
   */
  public void put(InputStream is,
                  long accessedExpireTimeout,
                  long modifiedExpireTimeout)
    throws IOException
  {
    long now = CurrentTime.getCurrentTime();
    long lastAccessTime = now;
    long lastModifiedTime = now;
    
    putStream(is,
              accessedExpireTimeout,
              modifiedExpireTimeout, 
              0,
              lastAccessTime,
              lastModifiedTime,
              0,
              false);
  }

  /**
   * Sets the value by an input stream
   */
  public void put(InputStream is,
                  long accessedExpireTimeout,
                  long modifiedExpireTimeout,
                  long lastAccessTime,
                  long lastModifiedTime)
    throws IOException
  {
    putStream(is,
              accessedExpireTimeout,
              modifiedExpireTimeout, 
              0,
              lastAccessTime,
              lastModifiedTime,
              0,
              false);
  }

  /**
   * Sets the value by an input stream
   */
  public void put(InputStream is,
                  long accessedExpireTimeout,
                  long modifiedExpireTimeout,
                  int flags)
    throws IOException
  {
    long now = CurrentTime.getCurrentTime();
    long lastAccessTime = now;
    long lastModifiedTime = now;
    
    putStream(is,
              accessedExpireTimeout, 
              modifiedExpireTimeout,
              flags,
              lastAccessTime,
              lastModifiedTime,
              0,
              false);
  }

  /**
   * Sets the value by an input stream
    */
  public void putIfNewer(long version, InputStream is)
    throws IOException
  {
    long now = CurrentTime.getCurrentTime();
    long lastAccessTime = now;
    long lastModifiedTime = now;
    
    CacheConfig config = getConfig();
    
    putStream(is,
              config.getAccessedExpireTimeout(),
              config.getModifiedExpireTimeout(), 
              0,
              lastAccessTime,
              lastModifiedTime,
              version,
              false);
  }

  private final void putStream(InputStream is,
                               long accessedExpireTime,
                               long modifiedExpireTime,
                               int userFlags,
                               long lastAccessTime,
                               long lastModifiedTime,
                               long newVersion,
                               boolean isLocal)
    throws IOException
  {
    loadLocalMnodeValue();

    DataItemLocal valueItem = getLocalDataManager().writeData(is);
    
    long valueHash = valueItem.getValueHash();
    long valueDataId = valueItem.getValueDataId();
    long valueDataTime = valueItem.getValueDataTime();
    
    long valueLength = valueItem.getLength();
    
    MnodeEntry mnodeEntry = getMnodeEntry();
    
    if (newVersion <= 0) {
      newVersion = getNewVersion(getMnodeEntry());
    }
    else if (newVersion < mnodeEntry.getVersion()) {
      log.finer(this + " put with obsolete version"
          + " current=0x" + Long.toHexString(mnodeEntry.getVersion())
          + " new=0x" + Long.toHexString(newVersion));
    }
    
    CacheConfig config = getConfig();
    
    long flags = config.getFlags() | ((long) userFlags) << 32;
    
    if (accessedExpireTime < 0)
      accessedExpireTime = config.getAccessedExpireTimeout();
    
    if (modifiedExpireTime < 0)
      modifiedExpireTime = config.getModifiedExpireTimeout();
    
    long now = CurrentTime.getCurrentTime();
    long delta = now - mnodeEntry.getLastAccessedTime();
    
    if (valueHash == mnodeEntry.getValueHash()
        && flags == mnodeEntry.getFlags()
        && delta < modifiedExpireTime
        && delta < accessedExpireTime) {
      // server/01nx
      
      return;
    }
    
    int leaseOwner = getMnodeEntry().getLeaseOwner();
    long leaseExpireTimeout = config.getLeaseExpireTimeout();
    
    MnodeUpdate mnodeUpdate = new MnodeUpdate(valueHash,
                                              valueLength,
                                              newVersion,
                                              flags,
                                              accessedExpireTime,
                                              modifiedExpireTime,
                                              leaseExpireTimeout,
                                              leaseOwner,
                                              lastAccessTime,
                                              lastModifiedTime);

    // add 25% window for update efficiency
    // idleTimeout = idleTimeout * 5L / 4;
    
    putLocalValue(mnodeUpdate, valueDataId, valueDataTime, null);
    config.getEngine().put(getKeyHash(), getCacheKey(), 
                           mnodeUpdate, 
                           valueDataId, valueDataTime);
    
    CacheWriterExt writer = config.getCacheWriterExt();

    if (! isLocal && writer != null && config.isWriteThrough()) {
      // loadValue(config);
      
      writer.write(this);
    }
  }

  /**
   * Sets a cache entry
   */
  public final boolean remove()
  {
    HashKey key = getKeyHash();

    MnodeEntry mnodeEntry = loadLocalMnodeValue();
    long oldValueHash = mnodeEntry.getValueHash();

    long newVersion = getNewVersion(mnodeEntry);

    /*
    long leaseTimeout = (mnodeEntry != null
                         ? mnodeEntry.getLeaseTimeout()
                         : config.getLeaseExpireTimeout());
    int leaseOwner = (mnodeEntry != null ? mnodeEntry.getLeaseOwner() : -1);
    */
    
    CacheConfig config = getConfig();
    
    MnodeUpdate mnodeUpdate = MnodeUpdate.createNull(newVersion, config);

    /*
    if (mnodeEntry != null)
      mnodeUpdate = MnodeUpdate.createNull(newVersion, mnodeEntry);
    else
      mnodeUpdate = MnodeUpdate.createNull(newVersion, config);
      */

    putLocalValueImpl(mnodeUpdate, 0, 0, null);
    
    config.getEngine().remove(key, getCacheKey(), mnodeUpdate);
    
    CacheWriterExt writer = config.getCacheWriterExt();
    
    if (writer != null && config.isWriteThrough()) {
      writer.delete(this);
    }

    return oldValueHash != 0;
  }
  
  //
  // atomic operations
  //

  /**
   * Sets the value by an input stream
   */
  public boolean putIfNew(MnodeUpdate update,
                          InputStream is)
    throws IOException
  {
    MnodeEntry entry = getMnodeEntry();
    
    if (update.getVersion() < entry.getVersion()) {
      return false;
    }
    else if (update.getVersion() == entry.getVersion()
             && update.getValueHash() == entry.getValueHash()) {
      return false;
    }
    
    MnodeValue newValue = putLocalValue(update, is);
    
    entry = getMnodeEntry();
    
    if (newValue.getValueHash() == update.getValueHash()) {
      getEngine().put(getKeyHash(), getCacheKey(),
                      update, 
                      entry.getValueDataId(),
                      entry.getValueDataTime());
    }

    return newValue.getValueHash() == update.getValueHash();
  }
  

  //
  // compare and put
  //
  
  public boolean compareAndPut(long testValue,
                               Object value)
  {
    MnodeEntry oldMnodeEntry = getMnodeEntry();
    
    CacheConfig config = getConfig();
    
    DataItemLocal dataItem
      = getLocalDataManager().writeValue(getMnodeEntry(), value, config);
    long valueDataId = dataItem.getValueDataId();
    long valueDataTime = dataItem.getValueDataTime();
    long newVersion = getNewVersion(oldMnodeEntry);
    
    // long oldValueDataId = oldMnodeEntry.getValueDataId();
    
    
    try {
      MnodeUpdate update = new MnodeUpdate(dataItem.getValueHash(),
                                           dataItem.getLength(),
                                           newVersion,
                                           config);
//                                           oldMnodeEntry);
      
      return config.getEngine().compareAndPut(this, testValue,
                                              update, 
                                              valueDataId,
                                              valueDataTime);
    } finally {
      MnodeValue newMnodeValue = getMnodeEntry();
      
      if (newMnodeValue.getValueHash() != dataItem.getValueHash()) {
        getLocalDataManager().removeData(valueDataId, valueDataTime);
      }
      /*
      else if (oldValueDataId != 0) {
        getLocalDataManager().removeData(oldValueDataId);
      }
      */
    }
  }
  
  public final boolean compareAndPutLocal(long testValue,
                                          MnodeUpdate update,
                                          StreamSource source)
  {
    // long version = getNewVersion(getMnodeEntry());
    long version = update.getVersion();
    

    // cloud/60r0
    DataItemLocal dataItem = getLocalDataManager().writeData(update, version, source);
    
    long valueDataId = dataItem.getValueDataId();
    long valueDataTime = dataItem.getValueDataTime();
    
    Object value = null;
    
    return compareAndPutLocal(testValue, update, valueDataId, valueDataTime, value);
  }
  
  public boolean compareAndPutLocal(long testValueHash,
                                    MnodeUpdate update,
                                    long valueDataId,
                                    long valueDataTime,
                                    Object value)
  {
    MnodeEntry mnodeValue = loadLocalMnodeValue();

    long oldValueHash = mnodeValue.getValueHash();

    if (testValueHash == oldValueHash) {
    }
    else if (testValueHash == MnodeEntry.ANY_KEY && oldValueHash != 0) {
    }
    else {
      return false;
    }
    
    // add 25% window for update efficiency
    // idleTimeout = idleTimeout * 5L / 4;

    mnodeValue = putLocalValueImpl(update, valueDataId, valueDataTime, value);
    
    return (mnodeValue != null);
  }

  protected boolean compareAndPut(DistCacheEntry entry,
                                  long testValue,
                                  MnodeUpdate mnodeUpdate,
                                  long valueDataId,
                                  long valueDataTime,
                                  Object value,
                                  CacheConfig config)
  {
    CacheEngine engine = config.getEngine();

    return engine.compareAndPut(entry, testValue, mnodeUpdate, valueDataId, valueDataTime);
  }
  
  //
  // get and put
  //

  /**
   * Remove the value
   */
  public Object getAndRemove()
  {
    return getAndPut(null);
  }

  public Object getAndReplace(long testValue,
                              Object value)
  {
    long prevDataId = getMnodeEntry().getValueDataId();
    long prevDataTime = getMnodeEntry().getValueDataTime();
    
    if (compareAndPut(testValue, value)) {
      long result = -1;
      
      CacheConfig config = getConfig();
      
      return getLocalDataManager().readData(getKeyHash(),
                                            result,
                                            prevDataId,
                                            prevDataTime,
                                            config.getValueSerializer(),
                                            config);
    }
    else {
      return null;
    }
  }

  /**
   * Sets the current value
   */
  public Object getAndPut(Object value)
  {
    long now = CurrentTime.getCurrentTime();

    // server/60a0 - on server '4', need to read update from triad
    MnodeEntry mnodeValue = loadMnodeValue(now, true); // , false);

    return getAndPut(value, now, mnodeValue);
  }

  /**
   * Sets a cache entry
   */
  protected final Object getAndPut(Object value,
                                   long now,
                                   MnodeEntry mnodeValue)
  {
    CacheConfig config = getConfig();
    
    DataItemLocal dataItem
      = getLocalDataManager().writeValue(mnodeValue, value, config);
    
    long version = getNewVersion(getMnodeEntry());
    
    MnodeUpdate update = new MnodeUpdate(dataItem.getValueHash(),
                                         dataItem.getLength(),
                                         version,
                                         config);
    // int leaseOwner = mnodeValue.getLeaseOwner();

    InputStream is = getAndPut(update,
                               dataItem.getValueDataId(),
                               dataItem.getValueDataTime());

    if (is == null)
      return null;
    
    Object oldValue = getLocalDataManager().decodeValue(is, config.getValueSerializer());

    return oldValue;
  }
  
  public DataItem getAndPutLocal(MnodeUpdate mnodeUpdate,
                             StreamSource source)
  {
    long oldValueDataId = getMnodeEntry().getValueDataId();
    long oldValueDataTime = getMnodeEntry().getValueDataTime();
    
    DataItemLocal dataItem = getLocalDataManager().writeData(source);
    
    long valueDataId = dataItem.getValueDataId();
    long valueDataTime = dataItem.getValueDataTime();
    
    Object value = null;

    putLocalValue(mnodeUpdate, valueDataId, valueDataTime, value);

    return new DataItem(oldValueDataId, oldValueDataTime);
  }
  
  public long getAndPutLocal(DistCacheEntry entry,
                             MnodeUpdate mnodeUpdate,
                             long valueDataId,
                             long valueDataTime,
                             Object value)
  {
    long oldValueHash = entry.getMnodeEntry().getValueHash();

    entry.putLocalValue(mnodeUpdate, valueDataId, valueDataTime, value);

    return oldValueHash;
  }

  /**
   * Sets a cache entry
   */
  private InputStream getAndPut(MnodeUpdate mnodeValue,
                                long valueDataId,
                                long valueDataTime)
  {
    return getEngine().getAndPut(this, mnodeValue, valueDataId, valueDataTime);
  }
  
  //
  // utility
  //

  /**
   * Sets the current value.
   */
  public final boolean compareAndSetEntry(MnodeEntry oldMnodeValue,
                                          MnodeEntry mnodeValue)
  {
    if (mnodeValue == null)
      throw new NullPointerException();
    
    return _mnodeEntry.compareAndSet(oldMnodeValue, mnodeValue);
  }
  
  /**
   * Writes the data to a stream.
   */
  public boolean readData(OutputStream os, CacheConfig config)
    throws IOException
  {
    return getLocalDataManager().readData(getKeyHash(), getMnodeEntry(),
                                          os, config);
  }

  public boolean isModified(MnodeValue newValue)
  {
    MnodeEntry oldValue = getMnodeEntry();
    
    if (oldValue.getVersion() < newValue.getVersion()) {
      return true;
    }
    else if (newValue.getVersion() < oldValue.getVersion()) {
      return false;
    }
    else {
      // XXX: need to check hash.
      return true;
    }
  }
  
  private long getNewVersion(MnodeValue mnodeValue)
  {
    long version = mnodeValue != null ? mnodeValue.getVersion() : 0;
    
    return getNewVersion(version);
  }
  
  private long getNewVersion(long version)
  {
    long newVersion = version + 1;

    long now = CurrentTime.getCurrentTime();
  
    if (newVersion < now)
      return now;
    else
      return newVersion;
  }

  public void clearLease(int oldLeaseOwner)
  {
    getMnodeEntry().clearLease(oldLeaseOwner);
  }

  public void clearLease()
  {
    getMnodeEntry().clearLease();
  }
  
  public boolean isLeaseExpired()
  {
    return getMnodeEntry().isLeaseExpired(CurrentTime.getCurrentTime());
  }
  
  public void updateLease(int leaseOwner)
  {
    if (leaseOwner <= 2) {
      return;
    }
    
    long now = CurrentTime.getCurrentTime();

    MnodeEntry entry = getMnodeEntry();
    
    if (isLeaseExpired() || entry.getLeaseOwner() == leaseOwner) {
      entry.setLeaseOwner(leaseOwner, now);
    }
  }

  public long getCost()
  {
    return 0;
  }
  
  //
  // get/load operations
  //
  
  public void load()
  {
    long now = CurrentTime.getCurrentTime();
    
    loadMnodeValue(now, true);
  }
  
  public void load(DistCacheLoadListener listener)
  {
    loadMnodeValue(listener);
  }

  private Object get(long now)
  {
    MnodeEntry mnodeEntry = loadMnodeValue(now, true);

    if (mnodeEntry == null) {
      return null;
    }

    Object value = mnodeEntry.getValue();

    if (value != null) {
      return value;
    }

    long valueHash = mnodeEntry.getValueHash();

    if (valueHash == 0) {
      return null;
    }
    
    updateAccessTime(mnodeEntry, now);
    // asdf
    
    CacheConfig config = getConfig();

    value = _cacheService.getLocalDataManager().readData(getKeyHash(),
                                                         valueHash,
                                                         mnodeEntry.getValueDataId(),
                                                         mnodeEntry.getValueDataTime(),
                                                         config.getValueSerializer(),
                                                         config);
    
    if (value == null) {
      // Recovery from dropped or corrupted data
      log.warning("Missing or corrupted data in get for " 
                  + mnodeEntry + " " + this);
      remove();
    }

    if (! config.isStoreByValue() || isImmutable(value)) {
      mnodeEntry.setObjectValue(value);
    }

    return value;
  }
  
  private boolean isImmutable(Object value)
  {
    return value instanceof String;
  }
  
  public long getValueHash()
  {
    MnodeEntry entry = getMnodeEntry();

    return entry.getValueHash();
  }
  
  public long getVersion()
  {
    MnodeEntry entry = getMnodeEntry();

    return entry.getVersion();
  }

  public Object getValue()
  {
    long now = CurrentTime.getCurrentTime();
    
    MnodeEntry entry = getMnodeEntry();
    
    return getValue(entry, now);
  }
  
  private Object getValue(MnodeEntry mnodeEntry,
                          long now)
    {
    if (mnodeEntry == null) {
      return null;
    }

    Object value = mnodeEntry.getValue();

    if (value != null) {
      return value;
    }

    long valueHash = mnodeEntry.getValueHash();

    if (valueHash == 0) {
      return null;
    }
    
    updateAccessTime(mnodeEntry, now);
    
    CacheConfig config = getConfig();

    value = _cacheService.getLocalDataManager().readData(getKeyHash(),
                                                         valueHash,
                                                         mnodeEntry.getValueDataId(),
                                                         mnodeEntry.getValueDataTime(),
                                                         config.getValueSerializer(),
                                                         config);
    
    if (value == null) {
      // Recovery from dropped or corrupted data
      log.warning("Missing or corrupted data in get for " 
                  + mnodeEntry + " " + this);
      remove();
    }

    mnodeEntry.setObjectValue(value);

    return value;
  }

  final private MnodeEntry loadMnodeValue(long now,
                                          boolean isUpdateAccessTime)
  {
    MnodeEntry mnodeEntry = loadLocalMnodeValue();
    
    CacheConfig config = getConfig();
    int server = config.getServerIndex();

    if (mnodeEntry == null 
        || mnodeEntry.isLocalExpired(server, now, config)
        || ! isReadThroughLocalValid(now)) {
      reloadValue(now, isUpdateAccessTime);
    }
    
    // server/016q
    if (isUpdateAccessTime) {
      updateAccessTime();
    }

    mnodeEntry = getMnodeEntry();

    return mnodeEntry;
  }

  final private void loadMnodeValue(DistCacheLoadListener listener)
  {
    MnodeEntry mnodeEntry = loadLocalMnodeValue();
    
    CacheConfig config = getConfig();
    
    int server = config.getServerIndex();
    
    long now = CurrentTime.getCurrentTime();
    
    if (mnodeEntry == null 
        || mnodeEntry.isLocalExpired(server, now, config)) {
      DistCacheLoadTask task = new DistCacheLoadTask(this, listener);
      
      _cacheService.schedule(task);
    }
    else {
      updateAccessTime();
      
      listener.onLoad(this);
    }
  }

  void reloadValue(long now,
                   boolean isUpdateAccessTime)
  {
    // only one thread may update the expired data
    if (startReadUpdate()) {
      try {
        loadExpiredValue(now, isUpdateAccessTime);
      } finally {
        finishReadUpdate();
      }
    }
  }
  
  private void loadExpiredValue(long now,
                                boolean isUpdateAccessTime)
  {
    MnodeEntry mnodeEntry = getMnodeEntry();
    
    _loadCount.incrementAndGet();
    
    CacheConfig config = getConfig();

    CacheEngine engine = config.getEngine();
    
    engine.get(this);
    
    mnodeEntry = getMnodeEntry();

    if (! mnodeEntry.isExpired(now) && isReadThroughLocalValid(now)) {
      if (isUpdateAccessTime) {
        mnodeEntry.setLastAccessTime(now);
      }
    }
    else if (loadFromCacheLoader(now)) {
      _lastLoaderTime = now; 
      mnodeEntry.setLastAccessTime(now);
    }
    else {
      MnodeEntry nullMnodeValue = new MnodeEntry(0, 0, mnodeEntry.getVersion(),
                                                 0,
                                                 config.getAccessedExpireTimeout(),
                                                 config.getModifiedExpireTimeout(),
                                                 config.getLeaseExpireTimeout(),
                                                 0, 0, null,
                                                 now, now,
                                                 true, true);

      compareAndSetEntry(mnodeEntry, nullMnodeValue);
    }
  }
  
  private boolean isReadThroughLocalValid(long now)
  {
    CacheConfig config = getConfig();
    
    if (! config.isReadThrough()) {
      return true;
    }
    else  {
      return (now - _lastLoaderTime < config.getReadThroughExpireTimeout());
    }
  }
  
  private boolean loadFromCacheLoader(long now)
  {
    CacheConfig config = getConfig();
    CacheLoaderExt loader = config.getCacheLoaderExt();

    if (loader != null && config.isReadThrough() && getKey() != null) {
      DistCacheEntryLoadCallback cb = new DistCacheEntryLoadCallback();

      loader.load(this, cb);

      return cb.get();
    }
    
    return false;
  }

  final void loadLocalEntry()
  {
    long now = CurrentTime.getCurrentTime();

    if (getMnodeEntry().isExpired(now) || ! isReadThroughLocalValid(now)) {
      forceLoadMnodeValue();
    }
  }

  /**
   * Gets a cache entry
   */
  private MnodeEntry forceLoadMnodeValue()
  {
    HashKey key = getKeyHash();
    MnodeEntry mnodeValue = getMnodeEntry();

    MnodeEntry newMnodeValue
      = _cacheService.getDataBacking().loadLocalEntryValue(key);
    
    if (newMnodeValue != null) {
      compareAndSetEntry(mnodeValue, newMnodeValue);
    }

    return getMnodeEntry();
  }

  /**
   * Gets a cache entry as a stream
   */
  final public boolean getStream(OutputStream os)
    throws IOException
  {
    long now = CurrentTime.getCurrentTime();
    
    MnodeEntry mnodeValue = loadMnodeValue(); // , false);

    if (mnodeValue == null) {
      return false;
    }

    updateAccessTime(mnodeValue, now);

    long valueHash = mnodeValue.getValueHash();

    if (valueHash == 0) {
      return false;
    }

    CacheConfig config = getConfig();

    return getLocalDataManager().readData(getKeyHash(), mnodeValue, os, config);
  }

  /**
   * Gets a cache entry as a stream
   */
  final public boolean getLocalStream(OutputStream os)
    throws IOException
  {
    long now = CurrentTime.getCurrentTime();

    MnodeEntry mnodeValue = getMnodeEntry();

    if (mnodeValue == null)
      return false;

    long valueHash = mnodeValue.getValueHash();

    if (valueHash == 0) {
      return false;
    }
    
    CacheConfig config = getConfig();

    return getLocalDataManager().readData(getKeyHash(), mnodeValue, os, config);
  }

  //
  // put methods
  //

  public MnodeUpdate localUpdate(MnodeUpdate update,
                                 InputStream is)
  {
    MnodeEntry oldEntryValue = getMnodeEntry();
    
    long oldEntryHash = oldEntryValue.getValueHash();
    long oldValueDataId = oldEntryValue.getValueDataId();
    long oldValueDataTime = oldEntryValue.getValueDataTime();
    DataItemLocal data = null;
    
    if (update.getValueHash() == 0) {
    }
    else if (oldEntryValue == null
             || oldEntryValue.getVersion() < update.getVersion()
             || (oldEntryValue.getVersion() == update.getVersion()
                 && update.getValueHash() < oldEntryHash)) {
      try {
        if (is != null) {
          data = _cacheService.getLocalDataManager().writeData(update,
                                                               update.getVersion(),
                                                               is);
        }
      } finally {
        IoUtil.close(is);
      }
    }
    
    if (data != null) {
      putLocalValueImpl(update, data.getValueDataId(), data.getValueDataTime(), null);
    }
    else {
    //  XXX: avoid update if no change?
      putLocalValueImpl(update, oldValueDataId, oldValueDataTime, null);
    }
    
    return getMnodeEntry().getRemoteUpdate();
  }

  /**
   * Sets a cache entry
   */
  public final MnodeValue putLocalValue(MnodeUpdate mnodeUpdate,
                                        InputStream is)
  {
    MnodeValue mnodeValue = localUpdate(mnodeUpdate, is);

    _cacheService.notifyPutListeners(getKeyHash(), getCacheKey(),
                                     mnodeUpdate, mnodeValue);
    
    return mnodeValue;
  }

  /**
   * Sets a cache entry
   */
  public final MnodeEntry putLocalValue(MnodeUpdate mnodeUpdate,
                                        DataItem valueData,
                                        Object value)
  {
    long valueDataId = 0;
    long valueDataTime = 0;
    
    if (valueData != null) {
      valueDataId = valueData.getId();
      valueDataTime = valueData.getTime();
    }

    return putLocalValue(mnodeUpdate, valueDataId, valueDataTime, value);
  }

  /**
   * Sets a cache entry
   */
  public final MnodeEntry putLocalValue(MnodeUpdate mnodeUpdate,
                                        long valueDataId,
                                        long valueDataTime,
                                        Object value)
  {
    // long valueHash = mnodeUpdate.getValueHash();
    // long version = mnodeUpdate.getVersion();
    
    MnodeEntry prevMnodeValue = getMnodeEntry();

    MnodeEntry mnodeValue = putLocalValueImpl(mnodeUpdate, 
                                              valueDataId,
                                              valueDataTime,
                                              value);
    if (mnodeValue.getValueHash() != prevMnodeValue.getValueHash()) {
      _cacheService.notifyPutListeners(getKeyHash(), getCacheKey(),
                                       mnodeUpdate, mnodeValue);
    }

    return mnodeValue;
  }

  /**
   * Sets a cache entry
   */
  private final MnodeEntry putLocalValueImpl(MnodeUpdate mnodeUpdate,
                                             long valueDataId,
                                             long valueDataTime,
                                             Object value)
  {
    HashKey key = getKeyHash();
    
    long valueHash = mnodeUpdate.getValueHash();
    long version = mnodeUpdate.getVersion();

    MnodeEntry oldEntryValue = getMnodeEntry();
    MnodeEntry mnodeValue;
    
    int oldLeaseOwner = oldEntryValue.getLeaseOwner();

    do {
      oldEntryValue = loadLocalMnodeValue();
    
      long oldValueHash
        = oldEntryValue != null ? oldEntryValue.getValueHash() : 0;

      long oldVersion = oldEntryValue != null ? oldEntryValue.getVersion() : 0;
      
      if (version < oldVersion
          || (version == oldVersion
              && valueHash != 0
              && valueHash <= oldValueHash)) {
        // server/01ns
        // lease ownership handled externally. Only lease owner updates.
        /*
        // lease ownership updates even if value doesn't
        if (oldEntryValue.isLeaseExpired(now)) {
          oldEntryValue.setLeaseOwner(mnodeUpdate.getLeaseOwner(), now);

          // XXX: access time?
          oldEntryValue.setLastAccessTime(now);
        }
        */

        return oldEntryValue;
      }

      // long accessTime = now;
      long accessTime = mnodeUpdate.getLastAccessTime();
      long updateTime = mnodeUpdate.getLastModifiedTime();
      
      int leaseOwner = oldLeaseOwner;
      
      // server/01ns
      /*
      if (oldEntryValue.isLeaseExpired(now))
        leaseOwner = mnodeUpdate.getLeaseOwner();
      else
        leaseOwner = oldLeaseOwner;
        */
      
      Object saveValue = null;
      if (! getConfig().isStoreByValue() || isImmutable(value)) {
        saveValue = value;
      }


      mnodeValue = new MnodeEntry(mnodeUpdate,
                                  valueDataId,
                                  valueDataTime,
                                  saveValue,
                                  accessTime,
                                  updateTime,
                                  true,
                                  false,
                                  leaseOwner);
    } while (! compareAndSetEntry(oldEntryValue, mnodeValue));

    //MnodeValue newValue
    _cacheService.getDataBacking().putLocalValue(mnodeValue, key,
                                                 getCacheKey(),
                                                 oldEntryValue,
                                                 mnodeUpdate);
    
    if (oldLeaseOwner != mnodeUpdate.getLeaseOwner()) {
      clearLease(oldLeaseOwner);
      _cacheService.getCacheEngine().notifyLease(key, 
                                                 getCacheKey(),
                                                 oldLeaseOwner);
    }
    
    return mnodeValue;
  }

  /**
   * Sets a cache entry
   */
  protected final void put(Object value,
                           long now,
                           MnodeEntry mnodeEntry,
                           boolean isWriteThrough)
  {
    // long idleTimeout = config.getIdleTimeout() * 5L / 4;
    HashKey key = getKeyHash();
    
    CacheConfig config = getConfig();

    DataItemLocal dataItem
      = _cacheService.getLocalDataManager().writeValue(mnodeEntry, value, config);
    
    long version = getNewVersion(mnodeEntry);

    MnodeUpdate update = new MnodeUpdate(dataItem.getValueHash(),
                                         dataItem.getLength(),
                                         version,
                                         config);
    
    mnodeEntry = putLocalValueImpl(update,
                                   dataItem.getValueDataId(), 
                                   dataItem.getValueDataTime(), 
                                   value);

    if (mnodeEntry == null) {
      return;
    }
    
    if ((update.getValueHash() != 0) != (dataItem.getValueDataId() != 0)) {
      throw new IllegalStateException(L.l("{0}: update: {1} dataItem: {2}",
                                          this, update, dataItem));
    }

    config.getEngine().put(key, getCacheKey(),
                           update, 
                           dataItem.getValueDataId(),
                           dataItem.getValueDataTime());
    
    CacheWriterExt writer = config.getCacheWriterExt();
    
    if (isWriteThrough && writer != null && config.isWriteThrough()) {
      // XXX: save facade?
      // writer.write(new ExtCacheEntryFacade(this));
      
      writer.write(this);
    }

    return;
  }

  /**
   * Sets a cache entry
   */
  public final MnodeEntry putLocalValue(MnodeEntry mnodeValue)
  {
    MnodeEntry oldEntryValue = getMnodeEntry();

    if (oldEntryValue != null && mnodeValue.compareTo(oldEntryValue) <= 0) {
      return oldEntryValue;
    }

    // the failure cases are not errors because this put() could
    // be immediately followed by an overwriting put()
    if (! compareAndSetEntry(oldEntryValue, mnodeValue)) {
      log.fine(this + " mnodeValue update failed due to timing conflict"
        + " (key=" + getKeyHash() + ")");

      return getMnodeEntry();
    }

    _cacheService.getDataBacking().insertLocalValue(getKeyHash(),
                                                    getCacheKey(),
                                                    mnodeValue,
                                                    oldEntryValue);
    
    return getMnodeEntry();
  }

  /**
   * Loads the value from the local store.
   */
  final MnodeEntry loadLocalMnodeValue()
  {
    HashKey key = getKeyHash();
    MnodeEntry mnodeValue = getMnodeEntry();

    if (mnodeValue.isImplicitNull()) {
      // MnodeEntry newMnodeValue = _cacheSystem.getDataBacking().loadLocalEntryValue(key);
      MnodeEntry newMnodeValue
        = _cacheService.getDataBacking().loadLocalEntryValue(key);

      if (newMnodeValue == null) {
        newMnodeValue = MnodeEntry.NULL;
      }
      
      // cloud/6811
      compareAndSetEntry(mnodeValue, newMnodeValue);

      mnodeValue = getMnodeEntry();
    }

    return mnodeValue;
  }
  
  public void updateModifiedTime()
  {
    MnodeEntry mnodeValue = getMnodeEntry();
    long now = CurrentTime.getCurrentTime();
    
    MnodeEntry newMnodeValue = mnodeValue.updateModifiedTime(now);
    
    compareAndSetEntry(mnodeValue, newMnodeValue);
  }

  void updateAccessTime(MnodeEntry mnodeValue,
                        long now)
  {
    if (mnodeValue != null) {
      long idleTimeout = mnodeValue.getAccessedExpireTimeout();
      long updateTime = mnodeValue.getLastModifiedTime();

      if (idleTimeout < CacheConfig.TIME_INFINITY
          && updateTime + mnodeValue.getAccessExpireTimeoutWindow() < now) {
        mnodeValue.setLastAccessTime(now);

        saveUpdateTime(mnodeValue);
      }
    }
  }

  final protected void updateAccessTime()
  {
    MnodeEntry mnodeValue = getMnodeEntry();
    
    long accessedExpireTimeout = mnodeValue.getAccessedExpireTimeout();
    long accessedTime = mnodeValue.getLastAccessedTime();

    long now = CurrentTime.getCurrentTime();
                       
    if (accessedExpireTimeout < CacheConfig.TIME_INFINITY
        && accessedTime + mnodeValue.getAccessExpireTimeoutWindow() < now) {
      mnodeValue.setLastAccessTime(now);

      saveUpdateTime(mnodeValue);
    }
  }

  /**
   * Sets a cache entry
   */
  final MnodeEntry saveUpdateTime(MnodeEntry mnodeValue)
  {
    MnodeEntry newEntryValue = saveLocalUpdateTime(mnodeValue);

    if (newEntryValue.getVersion() != mnodeValue.getVersion()) {
      return newEntryValue;
    }

    _cacheService.getCacheEngine().updateTime(getKeyHash(), 
                                              getCacheKey(),
                                              mnodeValue);
    
    CacheWriterExt writer = getConfig().getCacheWriterExt();

    if (writer != null && getConfig().isWriteThrough()) {
      writer.updateTime(this);
    }

    return mnodeValue;
  }

  /**
   * Sets a cache entry
   */
  final MnodeEntry saveLocalUpdateTime(MnodeEntry mnodeValue)
  {
    MnodeEntry oldEntryValue = getMnodeEntry();

    if (oldEntryValue != null
        && mnodeValue.getVersion() < oldEntryValue.getVersion()) {
      return oldEntryValue;
    }
    
    // cloud/60e3
    if (oldEntryValue != null
        && mnodeValue != oldEntryValue
        && mnodeValue.getLastAccessedTime() == oldEntryValue.getLastAccessedTime()
        && mnodeValue.getLastModifiedTime() == oldEntryValue.getLastModifiedTime()) {
      return oldEntryValue;
    }

    // the failure cases are not errors because this put() could
    // be immediately followed by an overwriting put()

    if (! compareAndSetEntry(oldEntryValue, mnodeValue)) {
      log.fine(this + " mnodeValue updateTime failed due to timing conflict"
               + " (key=" + getKeyHash() + ")");

      return getMnodeEntry();
    }
    
    _cacheService.getDataBacking().saveLocalUpdateTime(getKeyHash(),
                                                       mnodeValue,
                                                       oldEntryValue);

    return getMnodeEntry();
  }
  
  /**
   * Invalidates the entry
   */
  public void clear()
  {
    _mnodeEntry.set(MnodeEntry.NULL);
  }

  /**
   * Conditionally starts an update of a cache item, allowing only a
   * single thread to update the data.
   *
   * @return true if the thread is allowed to update
   */
  private final boolean startReadUpdate()
  {
    return _isReadUpdate.compareAndSet(false, true);
  }

  /**
   * Completes an update of a cache item.
   */
  private final void finishReadUpdate()
  {
    _isReadUpdate.set(false);
  }
  
  private long getNewVersion(MnodeEntry mnodeValue)
  {
    long version = mnodeValue != null ? mnodeValue.getVersion() : 0;
    
    return getNewVersion(version);
  }

  //
  // statistics
  //
  

  public int getLoadCount()
  {
    return _loadCount.get();
  }

  @Override
  public String toString()
  {
    return (getClass().getSimpleName()
            + "[key=" + _key
            + ",keyHash=" + Hex.toHex(_keyHash.getHash(), 0, 4)
            + ",owner=" + _owner
            + "]");
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy