Changeset 7715


Ignore:
Timestamp:
May 22, 2019, 8:31:13 AM (3 years ago)
Author:
Nicklas Nordborg
Message:

References #2139: Switch to Java 11 (or later)

Removed the DbControl.finalize() method and replaced it with a State class that also holds the database connections (via Hibernate).

In case the DbControl is not closed properly the State class log a warning message and close the database connections. It will no longer perform a full rollback of batchers and other actions (they may contain a reference to the DbControl? which would prevent a cleanup).

The Cleaner instance that was in the StaticCache has been moved to Application. Cleanup actions can be registered with Application.registerCleanup().

Location:
trunk/src/core/net/sf/basedb
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/core/net/sf/basedb/core/Application.java

    r7643 r7715  
    5454import java.util.TimerTask;
    5555import java.util.regex.Pattern;
     56import java.lang.ref.Cleaner;
     57import java.lang.ref.Cleaner.Cleanable;
    5658import java.net.InetAddress;
    5759import java.security.Security;
     
    129131  */
    130132  private static StaticCache staticCache;
     133
     134  /**
     135    For keeping track of objects that need to be cleaned up.
     136  */
     137  private static Cleaner cleaner;
    131138
    132139  /**
     
    490497      noAutoCompression = Config.getBoolean("autocompress.disable");
    491498      log.info("autocompress.disable = " + noAutoCompression);
     499     
     500      // Create cleaner instance
     501      cleaner = Cleaner.create();
    492502     
    493503      // Create a cache for SessionControl objects
     
    673683    ExtendedProperties.unload();
    674684    Config.unload();
    675 
     685   
     686    cleaner = null;
    676687    xtManager = null;
    677688    userFilesDirectory = null;
     
    725736  }
    726737
     738  /**
     739    Register an action that needs to be performed when the monitored
     740    object becomes unreachable. NOTE! If this is going to work the action
     741    MUST NOT contain a reference to the monitor object (since then the
     742    monitor will never become unreachable). Note that there is no
     743    guarantee as to when the cleaning action is performed. There may be
     744    a long delay. For best performance it is recommended to explicitely
     745    call {@link Cleanable#clean()} as soon as possible.
     746   
     747    @see Cleaner#register(Object, Runnable)
     748    @return A cleanable, or null if the cleaner is not running
     749    @since 3.16
     750  */
     751  public static Cleanable registerCleanup(Object monitor, Runnable action)
     752  {
     753    return cleaner == null ? null : cleaner.register(monitor, action);
     754  }
     755 
    727756  /**
    728757    Get the static cache mananger.
  • trunk/src/core/net/sf/basedb/core/DbControl.java

    r7622 r7715  
    5656import org.hibernate.query.Query;
    5757
     58import java.lang.ref.Cleaner.Cleanable;
    5859import java.lang.reflect.Constructor;
    5960import java.lang.reflect.InvocationTargetException;
     
    8586 
    8687  /**
    87     To keep track of the code that created this DbControl.
    88   */
    89   private final Throwable calledFrom;
     88    The state is holding Hibernate database
     89    connections and transaction.
     90  */
     91  private final State state;
    9092 
    9193  /**
     
    9799 
    98100  private ProjectSpecificAnnotationsManager pspManager;
    99  
    100   /**
    101     The Hibernate session.
    102   */
    103   private SessionWrapper hSession;
    104  
    105   /**
    106     A Hibernate stateless session.
    107   */
    108   private StatelessSessionWrapper hStatelessSession;
    109  
    110   /**
    111     The Hibernate transaction.
    112   */
    113   private org.hibernate.Transaction hTransaction;
    114101 
    115102  /**
     
    175162    }
    176163
    177     hSession = new SessionWrapper(HibernateUtil.newSession(logInterceptor, null));
    178     hTransaction = HibernateUtil.newTransaction(hSession);
    179164    itemCache = new IdentityHashMap<BasicData, BasicItem>();
    180165    commitQueue = new LinkedHashMap<BasicItem,Transactional.Action>();
    181166    uniqueRandoms = new HashSet<String>();
    182167    isClosed = false;
    183     calledFrom = new Throwable("Please check the code to make sure that DbControl.close() " +
    184       "is always called. Stacktrace of code that created this DbControl:");
     168    state = new State();
     169    state.cleanable = Application.registerCleanup(this, state);
     170    state.hSession = new SessionWrapper(HibernateUtil.newSession(logInterceptor, null));
     171    state.hTransaction = HibernateUtil.newTransaction(state.hSession);
    185172  }
    186173
     
    225212    if (pspManager == null)
    226213    {
    227       pspManager = new ProjectSpecificAnnotationsManager(hSession);
     214      pspManager = new ProjectSpecificAnnotationsManager(state.hSession);
    228215      addTransactionalAction(pspManager);
    229216    }
     
    247234  org.hibernate.Session getHibernateSession()
    248235  {
    249     return hSession;
     236    return state.hSession;
    250237  }
    251238
     
    256243  org.hibernate.StatelessSession getStatelessSession()
    257244  {
    258     if (hSession != null && hStatelessSession == null)
    259     {
    260       hStatelessSession = new StatelessSessionWrapper(HibernateUtil.newStatelessSession(hSession));
    261     }
    262     return hStatelessSession;
     245    if (state.hSession != null && state.hStatelessSession == null)
     246    {
     247      state.hStatelessSession = new StatelessSessionWrapper(HibernateUtil.newStatelessSession(state.hSession));
     248    }
     249    return state.hStatelessSession;
    263250  }
    264251 
     
    357344    try
    358345    {
    359       HibernateUtil.rollback(hTransaction);
    360       HibernateUtil.close(hSession);
    361       if (hStatelessSession != null) HibernateUtil.close(hStatelessSession);
     346      HibernateUtil.rollback(state.hTransaction);
     347      HibernateUtil.close(state.hSession);
     348      if (state.hStatelessSession != null) HibernateUtil.close(state.hStatelessSession);
    362349    }
    363350    catch (Throwable ex)
     
    365352      log.warn("Exception during rollback in Hibernate", ex);
    366353    }
    367     hStatelessSession = null;
    368     hSession = null;
    369     hTransaction = null;
    370354
    371355    // Call TransactionalAction.onRollback()
     
    397381  private void cleanUp()
    398382  {
     383    state.explicitClean();
    399384    commitQueue.clear();
    400385    itemCache.clear();
     
    503488          {
    504489            if (activeProject != null) item.setProjectDefaults(activeProject);
    505             HibernateUtil.saveData(hSession, data);
     490            HibernateUtil.saveData(state.hSession, data);
    506491            item.onAfterInsert();
    507492          }
    508493          else if (action == Transactional.Action.DELETE)
    509494          {
    510             if (HibernateUtil.exists(hSession, item.getType().getDataClass(), item.getId()))
     495            if (HibernateUtil.exists(state.hSession, item.getType().getDataClass(), item.getId()))
    511496            {
    512497              if (item.isUsed()) throw new ItemInUseException(item.toString());
    513               HibernateUtil.deleteData(hSession, data);
     498              HibernateUtil.deleteData(state.hSession, data);
    514499              // Need flush so directories can be deleted recursiveley
    515               if (item instanceof Directory || item instanceof File) HibernateUtil.flush(hSession);
     500              if (item instanceof Directory || item instanceof File) HibernateUtil.flush(state.hSession);
    516501            }
    517502          }
     
    519504        }
    520505      }
    521       HibernateUtil.flush(hSession);
     506      HibernateUtil.flush(state.hSession);
    522507      // Call TransactionalAction.onBeforeCommit()
    523508      if (transactionalActions != null)
     
    528513        }
    529514      }
    530       HibernateUtil.commit(hTransaction);
    531       HibernateUtil.close(hSession);
    532       if (hStatelessSession != null) HibernateUtil.close(hStatelessSession);
    533       hSession = null;
    534       hTransaction = null;
     515      HibernateUtil.commit(state.hTransaction);
     516      HibernateUtil.close(state.hSession);
     517      if (state.hStatelessSession != null) HibernateUtil.close(state.hStatelessSession);
    535518    }
    536519    catch (RuntimeException ex)
     
    580563    DiskUsageData currentDiskUsage = dcData.getDiskUsage();  // Current disk usage information
    581564
    582     QuotaTypeData quotaType = HibernateUtil.loadData(hSession, QuotaTypeData.class, SystemItems.getId(dcItem.getQuotaTypeSystemId()));
     565    QuotaTypeData quotaType = HibernateUtil.loadData(state.hSession, QuotaTypeData.class, SystemItems.getId(dcItem.getQuotaTypeSystemId()));
    583566    Location location = dcItem.getLocation();
    584567    long bytes = dcItem.getBytes();
     
    614597      if (deltaBytes > 0)
    615598      {
    616         QuotaTypeData typeTotal = HibernateUtil.loadData(hSession, QuotaTypeData.class, SystemItems.getId(QuotaType.TOTAL));
     599        QuotaTypeData typeTotal = HibernateUtil.loadData(state.hSession, QuotaTypeData.class, SystemItems.getId(QuotaType.TOTAL));
    617600        QuotaTypeData typeSpecific = quotaType;
    618601       
     
    623606          when we call getQuotaValues below.
    624607        */
    625         UserData owner = HibernateUtil.loadData(hSession, UserData.class, dcData.getOwner().getId());
     608        UserData owner = HibernateUtil.loadData(state.hSession, UserData.class, dcData.getOwner().getId());
    626609        if (owner.getQuota() != null)
    627610        {
     
    706689    if (quotaType.getId() == SystemItems.getId(QuotaType.TOTAL))
    707690    {
    708       query = (Query<Long>)HibernateUtil.getPredefinedQuery(hSession, "GET_TOTAL_DISKUSAGE_FOR_USER");
     691      query = (Query<Long>)HibernateUtil.getPredefinedQuery(state.hSession, "GET_TOTAL_DISKUSAGE_FOR_USER");
    709692      /*
    710693        SELECT SUM(du.bytes)
     
    716699    else
    717700    {
    718       query = (Query<Long>)HibernateUtil.getPredefinedQuery(hSession, "GET_SPECIFIC_DISKUSAGE_FOR_USER");
     701      query = (Query<Long>)HibernateUtil.getPredefinedQuery(state.hSession, "GET_SPECIFIC_DISKUSAGE_FOR_USER");
    719702      /*
    720703        SELECT SUM(du.bytes)
     
    746729    if (quotaType.getId() == SystemItems.getId(QuotaType.TOTAL))
    747730    {
    748       query = (Query<Long>)HibernateUtil.getPredefinedQuery(hSession, "GET_TOTAL_DISKUSAGE_FOR_GROUP");
     731      query = (Query<Long>)HibernateUtil.getPredefinedQuery(state.hSession, "GET_TOTAL_DISKUSAGE_FOR_GROUP");
    749732      /*
    750733        SELECT SUM(du.bytes)
     
    756739    else
    757740    {
    758       query = (Query<Long>)HibernateUtil.getPredefinedQuery(hSession, "GET_SPECIFIC_DISKUSAGE_FOR_GROUP");
     741      query = (Query<Long>)HibernateUtil.getPredefinedQuery(state.hSession, "GET_SPECIFIC_DISKUSAGE_FOR_GROUP");
    759742      /*
    760743        SELECT SUM(du.bytes)
     
    884867    if (isClosed()) throw new ConnectionClosedException();
    885868    sc.updateLastAccess();
    886     BasicData data = HibernateUtil.loadData(hSession, dataClass, id);
     869    BasicData data = HibernateUtil.loadData(state.hSession, dataClass, id);
    887870    return getItem(itemClass, data);
    888871  }
     
    928911        if (li.isUninitialized())
    929912        {
    930           li.setSession((org.hibernate.internal.SessionImpl)hSession.getParentSession());
     913          li.setSession((org.hibernate.internal.SessionImpl)state.hSession.getParentSession());
    931914        }
    932915       
     
    11361119    if (isClosed()) throw new ConnectionClosedException();
    11371120    sc.updateLastAccess();
    1138     HibernateUtil.evictData(hSession, item.getData());
     1121    HibernateUtil.evictData(state.hSession, item.getData());
    11391122    commitQueue.remove(item);
    11401123    itemCache.remove(item.getData());
     
    11721155        if (item.hasPermission(Permission.RESTRICTED_WRITE))
    11731156        {
    1174           HibernateUtil.updateData(hSession, item.getData());
     1157          HibernateUtil.updateData(state.hSession, item.getData());
    11751158        }
    11761159        else
     
    11831166        if (item.hasPermission(Permission.READ))
    11841167        {
    1185           HibernateUtil.lockData(hSession, item.getData(), org.hibernate.LockOptions.NONE);
     1168          HibernateUtil.lockData(state.hSession, item.getData(), org.hibernate.LockOptions.NONE);
    11861169        }
    11871170        else
     
    12111194    sc.updateLastAccess();
    12121195    if (!isAttached(item)) reattachItem(item, false);
    1213     HibernateUtil.refresh(hSession, item.getData());
     1196    HibernateUtil.refresh(state.hSession, item.getData());
    12141197  }
    12151198 
     
    12441227    if (isClosed()) throw new ConnectionClosedException();
    12451228    sc.updateLastAccess();
    1246     HibernateUtil.initCollection(hSession, item.getData(), collectionName);
     1229    HibernateUtil.initCollection(state.hSession, item.getData(), collectionName);
    12471230  }
    12481231
     
    12901273 
    12911274  /**
    1292     Clean up if a bad client application forgot to close the connection.
    1293   */
    1294   @SuppressWarnings("deprecation")
    1295   @Override
    1296   protected void finalize()
    1297     throws Throwable
    1298   {
    1299     if (!isClosed())
    1300     {
    1301       SessionControl sc = getSessionControl();
    1302       log.warn("Found unclosed DbControl during finalize" +
    1303         "; isLoggedIn = " + sc.isLoggedIn() +
    1304         "; clientId = " + sc.getClientId() +
    1305         "; userId = " + sc.getLoggedInUserId(),
    1306         calledFrom
    1307       );
    1308       close();
    1309     }
    1310     super.finalize();
     1275    Main purpose is to write a warning to the log file in case some user code that
     1276    created a DbControl instance never called the close() method before the DbControl
     1277    became uncreachable. This can cause memory leaks. As an extra safety guarding agains
     1278    unclosed database connections this class also holds the Hibernate
     1279    session/transaction so that they can be closed. NOTE! This is NOT a replacement for
     1280    a proper call to close() or commit() since there are a lot of actions that are not
     1281    performed by this class. For example, Batchers and TransactionalActions are not
     1282    performed.
     1283    @since 3.16
     1284  */
     1285  static class State
     1286    implements Runnable
     1287  {
     1288    SessionWrapper hSession;
     1289    org.hibernate.Transaction hTransaction;
     1290    private StatelessSessionWrapper hStatelessSession;
     1291    Throwable stacktrace;
     1292    Cleanable cleanable;
     1293   
     1294    State()
     1295    {
     1296      stacktrace = new Throwable("Please check the code to make sure that DbControl.close() " +
     1297        "is always called. Stacktrace of code that created this DbControl:");
     1298    }
     1299
     1300    /**
     1301      Explicit cleanup of the DbControl is happening. The hSession,
     1302      hTransaction, etc. should already have been closed via commit()
     1303      or rollback(). We simply nullify everything and invoke the clean()
     1304      method to unregister ourselves from the Cleaner.
     1305    */
     1306    void explicitClean()
     1307    {
     1308      stacktrace = null;
     1309      hSession = null;
     1310      hTransaction = null;
     1311      hStatelessSession = null;
     1312      if (cleanable != null) cleanable.clean();
     1313    }
     1314   
     1315    @Override
     1316    public void run()
     1317    {
     1318      // We should never get here with non-null objects if all code behaves and call commit() or close()
     1319      if (stacktrace != null) log.warn("Found unclosed DbControl during finalize!", stacktrace);
     1320      if (hTransaction != null) HibernateUtil.rollback(hTransaction);
     1321      if (hSession != null) HibernateUtil.close(hSession);
     1322      if (hStatelessSession != null) HibernateUtil.close(hStatelessSession);
     1323    }
    13111324  }
    13121325 
  • trunk/src/core/net/sf/basedb/util/StaticCache.java

    r7713 r7715  
    3737import java.io.OutputStream;
    3838import java.io.Serializable;
    39 import java.lang.ref.Cleaner;
    4039import java.lang.ref.WeakReference;
    4140import java.lang.ref.Cleaner.Cleanable;
     
    5049import java.util.regex.Pattern;
    5150
     51import net.sf.basedb.core.Application;
    5252import net.sf.basedb.core.BaseException;
    5353import net.sf.basedb.core.InvalidDataException;
     
    8686    org.slf4j.LoggerFactory.getLogger(StaticCache.class);
    8787
    88   private final static Cleaner cleaner = Cleaner.create();
    89  
    9088  /**
    9189    Checks if the given key is a vilid cache entry key.
     
    650648      state = new State(out, lock, false);
    651649      state.calledFrom = new Throwable();
    652       cleanable = cleaner.register(this, state);
     650      cleanable = Application.registerCleanup(this, state);
    653651      key = lock.getKey();
    654652    }
     
    661659      log.debug("Releasing write lock on: " + key);
    662660      state.calledFrom = null; // Set to null to not get a warning in the log file
    663       cleanable.clean();
     661      if (cleanable != null) cleanable.clean();
    664662      closed = true;
    665663    }
     
    697695      state = new State(in, lock, true);
    698696      state.calledFrom = new Throwable();
    699       cleanable = cleaner.register(this, state);
     697      cleanable = Application.registerCleanup(this, state);
    700698      key = lock.getKey();
    701699    }
     
    708706      log.debug("Releasing read lock on: " + key);
    709707      state.calledFrom = null; // Set to null to not get a warning in the log file
    710       cleanable.clean();
     708      if (cleanable != null) cleanable.clean();
    711709      closed = true;
    712710    }
Note: See TracChangeset for help on using the changeset viewer.