Coverage Report - co.stateful.Atomic
 
Classes in this File Line Coverage Branch Coverage Complexity
Atomic
65%
25/38
7%
2/26
2.667
Atomic$1
16%
1/6
N/A
2.667
Atomic$AjcClosure1
100%
1/1
N/A
2.667
Atomic$AjcClosure3
100%
1/1
N/A
2.667
 
 1  2
 /**
 2  
  * Copyright (c) 2014-2016, stateful.co
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the stateful.co nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package co.stateful;
 31  
 
 32  
 import com.jcabi.aspects.Loggable;
 33  
 import com.jcabi.aspects.Tv;
 34  
 import com.jcabi.log.Logger;
 35  
 import java.io.IOException;
 36  
 import java.security.SecureRandom;
 37  
 import java.util.Random;
 38  
 import java.util.concurrent.Callable;
 39  
 import java.util.concurrent.TimeUnit;
 40  
 import java.util.concurrent.atomic.AtomicBoolean;
 41  
 import lombok.EqualsAndHashCode;
 42  
 import lombok.ToString;
 43  
 
 44  
 /**
 45  
  * Atomic block of code.
 46  
  *
 47  
  * <p>This class runs your {@link Callable} in a concurrent thread-safe
 48  
  * manner, using a lock from Stateful.co. For example:
 49  
  *
 50  
  * <pre> Callable&lt;String&gt; origin = new Callable&lt;String&gt;() {
 51  
  *   &#64;Override
 52  
  *   public String call() {
 53  
  *     // fetch it from a thread-critical resource
 54  
  *     // and return
 55  
  *   }
 56  
  * };
 57  
  * Lock lock = new RtSttc(new URN("urn:github:12345"), "token")
 58  
  *   .locks().lock("test");
 59  
  * String result = new Atomic&lt;String&gt;(origin, lock).call();</pre>
 60  
  *
 61  
  * <p>If you want to use {@link Runnable} instead, try static method
 62  
  * {@link java.util.concurrent.Executors#callable(Runnable)}. If you
 63  
  * want to avoid checked exceptions, use {@link #callQuietly()}.
 64  
  *
 65  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 66  
  * @version $Id$
 67  
  * @since 0.6
 68  
  * @param <T> Type of result
 69  
  * @link <a href="http://www.yegor256.com/2014/05/18/cloud-autoincrement-counters.html">Atomic Counters at Stateful.co</a>
 70  
  * @link <a href="http://www.yegor256.com/2014/12/04/synchronization-between-nodes.html">Synchronization Between Nodes</a>
 71  
  */
 72  
 @Loggable(Loggable.DEBUG)
 73  0
 @ToString
 74  0
 @EqualsAndHashCode(of = { "callable", "lock" })
 75  0
 public final class Atomic<T> implements Callable<T> {
 76  
 
 77  
     /**
 78  
      * Random.
 79  
      */
 80  1
     private static final Random RANDOM = new SecureRandom();
 81  
 
 82  
     /**
 83  
      * Callable to use.
 84  
      */
 85  
     private final transient Callable<T> callable;
 86  
 
 87  
     /**
 88  
      * Encapsulated lock to use.
 89  
      */
 90  
     private final transient Lock lock;
 91  
 
 92  
     /**
 93  
      * Maximum waiting time, in milliseconds.
 94  
      */
 95  
     private final transient long max;
 96  
 
 97  
     /**
 98  
      * Successfully locked?
 99  
      */
 100  
     private final transient AtomicBoolean locked;
 101  
 
 102  
     /**
 103  
      * Label for locking.
 104  
      */
 105  
     private final transient String label;
 106  
 
 107  
     /**
 108  
      * Public ctor (default maximum waiting time of five minutes).
 109  
      * @param clbl Callable to use
 110  
      * @param lck Lock to use
 111  
      */
 112  
     public Atomic(final Callable<T> clbl, final Lock lck) {
 113  2
         this(clbl, lck, "");
 114  2
     }
 115  
 
 116  
     /**
 117  
      * Public ctor (default maximum waiting time of five minutes).
 118  
      * @param clbl Callable to use
 119  
      * @param lck Lock to use
 120  
      * @param lbl Label to use for locking and unlocking (can be empty)
 121  
      */
 122  
     public Atomic(final Callable<T> clbl, final Lock lck, final String lbl) {
 123  2
         this(clbl, lck, lbl, TimeUnit.MINUTES.toMillis((long) Tv.FIVE));
 124  2
     }
 125  
 
 126  
     /**
 127  
      * Public ctor.
 128  
      * @param clbl Callable to use
 129  
      * @param lck Lock to use
 130  
      * @param lbl Label to use for locking and unlocking (can be empty)
 131  
      * @param maximum Maximum waiting time
 132  
      * @since 0.8
 133  
      * @checkstyle ParameterNumberCheck (5 lines)
 134  
      */
 135  
     public Atomic(final Callable<T> clbl, final Lock lck, final String lbl,
 136  2
         final long maximum) {
 137  2
         this.locked = new AtomicBoolean();
 138  2
         this.callable = clbl;
 139  2
         this.lock = lck;
 140  2
         this.max = maximum;
 141  2
         this.label = lbl;
 142  2
     }
 143  
 
 144  
     @Override
 145  
     public T call() throws Exception {
 146  4
         final Thread hook = new Thread(
 147  2
             new Runnable() {
 148  
                 @Override
 149  
                 public void run() {
 150  
                     try {
 151  0
                         Atomic.this.lock.unlock(Atomic.this.label);
 152  0
                     } catch (final IOException ex) {
 153  0
                         throw new IllegalStateException(ex);
 154  0
                     }
 155  0
                 }
 156  
             }
 157  
         );
 158  2
         Runtime.getRuntime().addShutdownHook(hook);
 159  2
         long attempt = 0L;
 160  2
         final long start = System.currentTimeMillis();
 161  2
         while (!this.lock.lock(this.label)) {
 162  0
             final long age = System.currentTimeMillis() - start;
 163  0
             if (age > this.max) {
 164  0
                 throw new IllegalStateException(
 165  
                     Logger.format(
 166  
                         // @checkstyle LineLength (1 line)
 167  
                         "lock \"%s\" is stale (%d failed attempts in %[ms]s, which is over %[ms]s)",
 168  
                         this.lock, attempt, age, this.max
 169  
                     )
 170  
                 );
 171  
             }
 172  0
             ++attempt;
 173  0
             final long delay = Math.min(
 174  
                 TimeUnit.MINUTES.toMillis(1L),
 175  
                 Math.abs(
 176  
                     (long) Tv.HUNDRED + (long) Atomic.RANDOM.nextInt(Tv.HUNDRED)
 177  
                     // @checkstyle MagicNumber (1 line)
 178  
                     + (long) StrictMath.pow(5.0d, (double) attempt + 1.0d)
 179  
                 )
 180  
             );
 181  0
             Logger.info(
 182  
                 this,
 183  
                 // @checkstyle LineLength (1 line)
 184  
                 "lock \"%s\" is occupied for %[ms]s already, attempt #%d in %[ms]s",
 185  
                 this.lock, age, attempt, delay
 186  
             );
 187  0
             TimeUnit.MILLISECONDS.sleep(delay);
 188  0
         }
 189  2
         this.locked.set(true);
 190  
         try {
 191  2
             return this.callable.call();
 192  
         } finally {
 193  2
             if (this.locked.get()) {
 194  2
                 this.lock.unlock(this.label);
 195  
             }
 196  2
             Runtime.getRuntime().removeShutdownHook(hook);
 197  2
             Logger.info(
 198  
                 this,
 199  
                 // @checkstyle LineLength (1 line)
 200  
                 "\"%s\" took %[ms]s after %d attempt(s)",
 201  
                 this.lock, System.currentTimeMillis() - start, attempt + 1L
 202  
             );
 203  
         }
 204  
     }
 205  
 
 206  
     /**
 207  
      * Call without exception throwing.
 208  
      * @return Result
 209  
      * @since 0.9
 210  
      */
 211  
     @SuppressWarnings("PMD.AvoidCatchingGenericException")
 212  
     public T callQuietly() {
 213  
         try {
 214  2
             return this.call();
 215  
             // @checkstyle IllegalCatchCheck (1 line)
 216  0
         } catch (final Exception ex) {
 217  0
             throw new IllegalStateException(ex);
 218  
         }
 219  
     }
 220  
 
 221  
 }