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

org.apache.jena.system.TxnCounter Maven / Gradle / Ivy

There is a newer version: 5.2.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.jena.system;

import static org.apache.jena.query.ReadWrite.WRITE ;

import java.util.concurrent.Semaphore ;
import java.util.concurrent.atomic.AtomicLong ;

import org.apache.jena.atlas.lib.Lib ;
import org.apache.jena.query.ReadWrite ;
import org.apache.jena.sparql.JenaTransactionException ;
import org.apache.jena.sparql.core.Transactional ;

/** A MR+SW transactional Counter */ 
public class TxnCounter implements Transactional {
    
    // ---- TransactionCoordinator.
    
    // Semaphore to implement "Single Active Writer" - independent of readers
    // This is not reentrant.
    private Semaphore writersWaiting = new Semaphore(1, true) ;
    
    private void releaseWriterLock() {
        int x = writersWaiting.availablePermits() ;
        if ( x != 0 )
            throw new JenaTransactionException("TransactionCoordinator: Probably mismatch of enable/disableWriter calls") ;
        writersWaiting.release() ;
    }
    
    private boolean acquireWriterLock(boolean canBlock) {
        if ( ! canBlock )
            return writersWaiting.tryAcquire() ;
        try { 
            writersWaiting.acquire() ; 
            return true;
        } catch (InterruptedException e) { throw new JenaTransactionException(e) ; }
    }
    // ---- TransactionCoordinator.
    
    // Transaction state.
    static class IntegerState {
        long txnValue ;
        public IntegerState(long v) { this.txnValue = v ; }
    }
    
    // Global state - the exterally visible value and the starting point for any
    // transaction. This is set to a new value when a write transaction commits.
    
    private final AtomicLong value = new AtomicLong(-1712) ; 

    // ---- Transaction state.
    
    // The per-transaction state (inside a transaction). Null outside a transaction
    // cleared by commit or abort in a write transaction.
    private ThreadLocal txnValue = ThreadLocal.withInitial(()->null) ;
    // The kind of transaction.
    private ThreadLocal    txnMode  = ThreadLocal.withInitial(()->null) ;
    
    // Syncrhonization for making changes.  
    private Object txnLifecycleLock   = new Object() ; 
    
    public TxnCounter(long x) {
        value.set(x) ;
    }
    
    @Override
    public void begin(ReadWrite readWrite) {
        begin(readWrite, true) ;
    }
    
    public void begin(ReadWrite readWrite, boolean canBlock) {
        // Ensure a single writer. 
        // (Readers never block at this point.)
        if ( readWrite == WRITE ) {
            // Writers take a WRITE permit from the semaphore to ensure there
            // is at most one active writer, else the attempt to start the
            // transaction blocks.
            // Released by in commit/abort.
            acquireWriterLock(canBlock) ;
        }
        // at this point, 
        // One writer or one of many readers. 
        
        synchronized(txnLifecycleLock) {
            if ( txnMode.get() != null )
                throw new JenaTransactionException("Already in a transaction") ;
            IntegerState state = new IntegerState(value.get()) ;
            txnValue.set(state) ;
            txnMode.set(readWrite);
        }
    }

    @Override
    public void commit() {
        checkTxn(); 
        if ( isWriteTxn() ) {
            // Set global.
            value.set(getDataState().txnValue) ;
            txnValue.set(null); 
            releaseWriterLock();
        }
        endOnce() ;
    }

    @Override
    public void abort() {
        checkTxn(); 
        if ( isWriteTxn() ) {
            txnValue.set(null); 
            releaseWriterLock();
        }
        endOnce() ;
    }

    @Override
    public boolean isInTransaction() {
        ReadWrite mode = txnMode.get() ;
        if ( mode == null )
            // Remove it - avoid holding the memory.
            txnMode.remove();
        return mode != null ;
    }

    @Override
    public void end() {
        if ( ! isInTransaction() ) 
            return ;
        if ( isWriteTxn() && txnValue.get() != null )
            throw new JenaTransactionException("No commit or abort before end for a write transaction") ;
        endOnce() ;
    }

    private void endOnce() {
        if ( isActiveTxn() ) {
            synchronized(txnLifecycleLock) {
                txnValue.remove();
                txnMode.remove();
            }
        }
    }

    /** Increment the value inside a write transaction */ 
    public void inc() {
        checkWriteTxn() ;
        IntegerState ts = getDataState() ;
        ts.txnValue++ ;
    }
    
    /** Decrement the value inside a write transaction */ 
    public void dec() {
        checkWriteTxn() ;
        IntegerState ts = getDataState() ;
        ts.txnValue-- ;
    }

    /** Set the value inside a write transaction, return the old value*/ 
    public long set(long x) {
        checkWriteTxn() ;
        IntegerState ts = getDataState() ;
        long v = ts.txnValue ;
        ts.txnValue = x ;
        return v ;
    }
    

    /** Return the current value in a transaction. 
     * Must be inside a transaction. 
     * @see #get
     */
    public long read() {
        checkTxn();
        return getDataState().txnValue ;
    }

    /** Return the current value.
     * If inside a transaction, return the transaction view of the value.
     * If not in a transaction return the state value (effectively
     * a read transaction, optimized by the fact that reading the
     * {@code TransInteger} state is atomic).
     */
    public long get() {
        if ( isActiveTxn() )
            return getDataState().txnValue ;
        else
            return value.get() ;
    }

    /** Read the current global state (that is, the last committed value) outside a transaction. */
    public long value() {
        return value.get() ;
    }

    // These two operations not clear the thread local if we are not in a transaction.
    // This is a potential memory leak.
    // Use "isInTransaction" to read and clear.

    /** Is this a write transaction? Shoudl be called inside a transaction. */
    private boolean isWriteTxn() {
        ReadWrite rw = txnMode.get() ;
        if ( rw == null )
            throw new JenaTransactionException(Lib.classShortName(this.getClass())+".isWriteTxn called outside a transaction") ;
        return txnMode.get() == ReadWrite.WRITE ;
    }

    private boolean isActiveTxn() {
        ReadWrite rw = txnMode.get() ;
        return rw != null ;
    }

    private IntegerState getDataState() {
        return txnValue.get() ;
    }

    private void checkWriteTxn() {
        if ( ! isWriteTxn() )
            throw new JenaTransactionException("Not in a write transaction") ;
    }

    private void checkTxn() {
        if ( ! isActiveTxn() )
            throw new JenaTransactionException("Not in a transaction") ;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy