Package com.ikanow.infinit.e.data_model.utils

Source Code of com.ikanow.infinit.e.data_model.utils.MongoTransactionLock

/*******************************************************************************
* Copyright 2012 The Infinit.e Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.ikanow.infinit.e.data_model.utils;

import java.net.InetAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import org.bson.types.ObjectId;

import com.ikanow.infinit.e.data_model.store.DbManager;
import com.ikanow.infinit.e.data_model.store.MongoDbManager;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;

//
// Use this to protect from race conditions between threads/processes/nodes during multiple short
// transactions (eg in tomcat handles)
//
// If you want to ensure that only one node is performing an action (eg polling a DB)
// then use MongoApplicationLock instead (see examples)
//

public class MongoTransactionLock {

  ///////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////
 
  // INTERFACE
 
  public static synchronized MongoTransactionLock getLock(String database)
  {
    if (null == _lockMap) {
      _lockMap = new HashMap<String, MongoTransactionLock>();     
    }
    MongoTransactionLock lock = _lockMap.get(database);
    if (null == lock) {
      lock = new MongoTransactionLock(database);
      _lockMap.put(database, lock);
    }
    return lock;
  }//TESTED
 
  // If you are sure all the clocks are synchronized, you can use this as stateless mechanism for
  // grabbing tokens (eg if you are not going to hang around to wait if a lock times out)
 
  public void clearStaleLocksOnTime(long ageout_secs) {
    Date ageout_date = new Date(new Date().getTime() - ageout_secs*1000L);
    // (sync doesn't matter here because the subsequent
    MongoDbManager.getCollection(_database, _lockname).remove(
        new BasicDBObject(lastUpdated_, new BasicDBObject(DbManager.lt_, ageout_date)));
  }
 
  ///////////////////////////////////////////////////////////////////////////
 
  public boolean acquire(long nTimeout_ms)
  {
    return acquire(nTimeout_ms, false);
  }
 
  public boolean acquire(long nTimeout_ms, boolean bTryToAcquireAfterTimeoutIfRemote)
  {
    try {
      long nNow = new Date().getTime();
      boolean bAcquire = _localLock.tryAcquire(1, nTimeout_ms, TimeUnit.MILLISECONDS);
     
      if (bAcquire) {     
        while (!getToken()) {
          Thread.sleep(50);
          if ((new Date().getTime() - nNow) > nTimeout_ms) {
            bAcquire = false;
            if (bTryToAcquireAfterTimeoutIfRemote) {
              bAcquire = updateToken(true);
            }
            break;
          }//TESTED
         
        } // (end while don't have remote control)
       
        if (!bAcquire) { // Got local mutex but not remote one so bail on local)
          _localLock.release();
        }
       
      } // (bAcquire if got local mutex)
      return bAcquire;
    }
    catch (InterruptedException e) {
      return false;
    }
  }//TESTED
 
  ///////////////////////////////////////////////////////////////////////////

  public void release() {   
    _localLock.release();
    removeToken();
  }//TESTED
 
  ///////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////

  // TOP LEVEL STATE
 
  public static final String LOCKNAME = "appserver_translock";
 
  protected static HashMap<String, MongoTransactionLock> _lockMap = null;
 
  protected String _database = null;
  protected String _lockname = null;
   
  protected MongoTransactionLock() {}
  protected MongoTransactionLock(String database) {
    _database = database;
    _lockname = LOCKNAME;
    _localLock = new Semaphore(1);
  }
  protected Semaphore _localLock;
 
  // Caches an instance of the collection object for each thread accessing this object
  protected ThreadLocal<DBCollection> _collections  = new ThreadLocal<DBCollection>() {
        @Override protected DBCollection initialValue() {
            try {
        return MongoDbManager.getCollection(_database, _lockname);
      } catch (Exception e) {
        return null;
      }
        }   
  }
 
  ///////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////
 
  // LOW LEVEL / SHARED STATE
 
  // Synchronization utility between multiple machines

  private String _savedHostname = "";
  private String _savedOneUp = "";
  private boolean _bHaveControl = false;
  private long _nLastCheck = 0;
 
  private final String id_ = "_id";
  private final String hostname_ = "hostname";
  private final String oneUp_ = "1up";
  private final String lastUpdated_ = "lastUpdated"; // (useable by a somewhat standalone utility)
 
  protected synchronized boolean getToken() {
    DBCollection cachedCollection = _collections.get();
   
    boolean bDefinitelyHaveControl = false;
    String hostname = null;
    String oneUp = null;
   
    // 1] Get Lock Object (create one an assert control if it doesn't exist)
   
    BasicDBObject lockObj = (BasicDBObject) cachedCollection.findOne();
    if (null == lockObj) { // Currently the DB is unlocked
      hostname = getHostname();
      oneUp = Long.toString(1000000L*(new Date().getTime() % 10000));
        // (ie a randomish start number)
     
      lockObj = new BasicDBObject(id_, _lockId);
      lockObj.put(hostname_, hostname);
      lockObj.put(oneUp_, oneUp);
      lockObj.put(lastUpdated_, new Date());
     
      //logger.debug("Creating a new aggregation lock object: " + lockObj.toString());
     
      try {
        cachedCollection.insert(lockObj, WriteConcern.SAFE);
          // (will fail if another harvester gets there first)
        bDefinitelyHaveControl = true;
      }
      catch (Exception e) { // Someone else has created it in the meantime
        lockObj = (BasicDBObject) cachedCollection.findOne();       
      }
     
    }//TESTED
   
    // (So by here lockObj is always non-null)
   
    // 2] Do I have control?
   
    if (bDefinitelyHaveControl) {
      _bHaveControl = true;
      _nLastCheck = 0;
    }
    else {
      hostname = lockObj.getString(hostname_);
      oneUp = lockObj.getString(oneUp_);     
      _bHaveControl = getHostname().equals(hostname);
    }
    // 3] If not, has the lock object been static for >= 1 minute
   
    if (!_bHaveControl) { // Don't currently have control
      long nNow = new Date().getTime();
      if (0 == _nLastCheck) {
        _nLastCheck = nNow;       
      }
     
      if ((nNow - _nLastCheck) > 60000) { // re-check every minute
        if (_savedHostname.equals(hostname) && _savedOneUp.equals(oneUp)) { // Now I have control...
          //logger.debug("I am taking control from: " + hostname + ", " + oneUp);
         
          if (updateToken(true)) { // Try to grab control:           
            _bHaveControl = true
          }
          else { // (else someone else snagged control just carry on)
            _nLastCheck = 0; // (reset clock again anyway)
          }
         

        }//(if lock has remained static)
      }//(end if >=1 minutes has passed)
     
    }//(end if have don't have control)

    // 4] Update saved state
   
    _savedHostname = hostname;
    _savedOneUp = oneUp;
   
    return _bHaveControl;
  }//TESTED

  ///////////////////////////////////////////////////////////////////////////
 
  protected synchronized void removeToken() {
    if (_bHaveControl) {     
      DBCollection cachedCollection = _collections.get();
     
      BasicDBObject queryObj = new BasicDBObject();
      queryObj.put(hostname_, getHostname());
        // (ie will only remove a lock I hold)
      cachedCollection.remove(queryObj);
      _bHaveControl = false;
    }   
  }//TESTED
 
  ///////////////////////////////////////////////////////////////////////////
 
  protected synchronized boolean updateToken(boolean bForce) {
    if (_bHaveControl || bForce) {
      DBCollection cachedCollection = _collections.get();
      BasicDBObject lockObj = new BasicDBObject();

      long nOneUp = Long.parseLong(_savedOneUp);
      lockObj.put(hostname_, getHostname());
      String newOneUp = Long.toString(nOneUp + 1);
      lockObj.put(oneUp_, newOneUp);
      lockObj.put(lastUpdated_, new Date());
      BasicDBObject queryObj = new BasicDBObject();
      queryObj.put(hostname_, _savedHostname);
      queryObj.put(oneUp_, _savedOneUp);
      WriteResult wr = cachedCollection.update(queryObj, new BasicDBObject(MongoDbManager.set_, lockObj), false, true);
        // (need the true in case the db is sharded)
     
      if (wr.getN() > 0) {
        _savedOneUp = newOneUp;
        _bHaveControl = true;
        _nLastCheck = 0;
        return true;
      }
      else {
        return false;
      }
    }
    return false;
  }//TESTED
 
  ///////////////////////////////////////////////////////////////////////////
 
  // Utility to get harvest name for display purposes
 
  private static ObjectId _lockId = new ObjectId("4f976e98d4eefff2ed6963dc");
 
  private static String _hostname = null;
  private static String getHostname() {
    // (just get the hostname once)
    if (null == _hostname) {
      try {
        _hostname = InetAddress.getLocalHost().getHostName();
      } catch (Exception e) {
        _hostname = "UNKNOWN";
      }
      if (null != _hostnameSuffix) {
        _hostname = _hostname + _hostnameSuffix;
      }
    }   
    return _hostname;
  }//TESTED 
 
  // For testing purposes:
  public static void setHostnameSuffix(String hostnameSuffix) {
    _hostnameSuffix = hostnameSuffix;
  }
  private static String _hostnameSuffix = null;
 
}
TOP

Related Classes of com.ikanow.infinit.e.data_model.utils.MongoTransactionLock

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.