View Javadoc
1   /*
2    * Copyright 2019 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.util.internal;
17  
18  import static io.netty.util.internal.ObjectUtil.checkPositive;
19  
20  import io.netty.util.IllegalReferenceCountException;
21  import io.netty.util.ReferenceCounted;
22  
23  /**
24   * Common logic for {@link ReferenceCounted} implementations
25   */
26  public abstract class ReferenceCountUpdater<T extends ReferenceCounted> {
27      /*
28       * Implementation notes:
29       *
30       * For the updated int field:
31       *   Even => "real" refcount is (refCnt >>> 1)
32       *   Odd  => "real" refcount is 0
33       *
34       * (x & y) appears to be surprisingly expensive relative to (x == y). Thus this class uses
35       * a fast-path in some places for most common low values when checking for live (even) refcounts,
36       * for example: if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) { ...
37       */
38  
39      protected ReferenceCountUpdater() {
40      }
41  
42      protected abstract void safeInitializeRawRefCnt(T refCntObj, int value);
43  
44      protected abstract int getAndAddRawRefCnt(T refCntObj, int increment);
45  
46      protected abstract int getRawRefCnt(T refCnt);
47  
48      protected abstract int getAcquireRawRefCnt(T refCnt);
49  
50      protected abstract void setReleaseRawRefCnt(T refCnt, int value);
51  
52      protected abstract boolean casRawRefCnt(T refCnt, int expected, int value);
53  
54      public final int initialValue() {
55          return 2;
56      }
57  
58      public final void setInitialValue(T instance) {
59          safeInitializeRawRefCnt(instance, initialValue());
60      }
61  
62      private static int realRefCnt(int rawCnt) {
63          return rawCnt != 2 && rawCnt != 4 && (rawCnt & 1) != 0 ? 0 : rawCnt >>> 1;
64      }
65  
66      /**
67       * Like {@link #realRefCnt(int)} but throws if refCnt == 0
68       */
69      private static int toLiveRealRefCnt(int rawCnt, int decrement) {
70          if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) {
71              return rawCnt >>> 1;
72          }
73          // odd rawCnt => already deallocated
74          throw new IllegalReferenceCountException(0, -decrement);
75      }
76  
77      public final int refCnt(T instance) {
78          return realRefCnt(getAcquireRawRefCnt(instance));
79      }
80  
81      public final boolean isLiveNonVolatile(T instance) {
82          final int rawCnt = getRawRefCnt(instance);
83          // The "real" ref count is > 0 if the rawCnt is even.
84          return rawCnt == 2 || rawCnt == 4 || rawCnt == 6 || rawCnt == 8 || (rawCnt & 1) == 0;
85      }
86  
87      /**
88       * An unsafe operation that sets the reference count directly
89       */
90      public final void setRefCnt(T instance, int refCnt) {
91          int rawRefCnt = refCnt > 0 ? refCnt << 1 : 1; // overflow OK here
92          setReleaseRawRefCnt(instance, rawRefCnt);
93      }
94  
95      /**
96       * Resets the reference count to 1
97       */
98      public final void resetRefCnt(T instance) {
99          // no need of a volatile set, it should happen in a quiescent state
100         setReleaseRawRefCnt(instance, initialValue());
101     }
102 
103     public final T retain(T instance) {
104         return retain0(instance, 1, 2);
105     }
106 
107     public final T retain(T instance, int increment) {
108         // all changes to the raw count are 2x the "real" change - overflow is OK
109         int rawIncrement = checkPositive(increment, "increment") << 1;
110         return retain0(instance, increment, rawIncrement);
111     }
112 
113     // rawIncrement == increment << 1
114     private T retain0(T instance, final int increment, final int rawIncrement) {
115         int oldRef = getAndAddRawRefCnt(instance, rawIncrement);
116         if (oldRef != 2 && oldRef != 4 && (oldRef & 1) != 0) {
117             throw new IllegalReferenceCountException(0, increment);
118         }
119         // don't pass 0!
120         if ((oldRef <= 0 && oldRef + rawIncrement >= 0)
121                 || (oldRef >= 0 && oldRef + rawIncrement < oldRef)) {
122             // overflow case
123             getAndAddRawRefCnt(instance, -rawIncrement);
124             throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
125         }
126         return instance;
127     }
128 
129     public final boolean release(T instance) {
130         int rawCnt = getRawRefCnt(instance);
131         return rawCnt == 2 ? tryFinalRelease0(instance, 2) || retryRelease0(instance, 1)
132                 : nonFinalRelease0(instance, 1, rawCnt, toLiveRealRefCnt(rawCnt, 1));
133     }
134 
135     public final boolean release(T instance, int decrement) {
136         int rawCnt = getRawRefCnt(instance);
137         int realCnt = toLiveRealRefCnt(rawCnt, checkPositive(decrement, "decrement"));
138         return decrement == realCnt ? tryFinalRelease0(instance, rawCnt) || retryRelease0(instance, decrement)
139                 : nonFinalRelease0(instance, decrement, rawCnt, realCnt);
140     }
141 
142     private boolean tryFinalRelease0(T instance, int expectRawCnt) {
143         return casRawRefCnt(instance, expectRawCnt, 1); // any odd number will work
144     }
145 
146     private boolean nonFinalRelease0(T instance, int decrement, int rawCnt, int realCnt) {
147         if (decrement < realCnt
148                 // all changes to the raw count are 2x the "real" change - overflow is OK
149                 && casRawRefCnt(instance, rawCnt, rawCnt - (decrement << 1))) {
150             return false;
151         }
152         return retryRelease0(instance, decrement);
153     }
154 
155     private boolean retryRelease0(T instance, int decrement) {
156         for (;;) {
157             int rawCnt = getRawRefCnt(instance), realCnt = toLiveRealRefCnt(rawCnt, decrement);
158             if (decrement == realCnt) {
159                 if (tryFinalRelease0(instance, rawCnt)) {
160                     return true;
161                 }
162             } else if (decrement < realCnt) {
163                 // all changes to the raw count are 2x the "real" change
164                 if (casRawRefCnt(instance, rawCnt, rawCnt - (decrement << 1))) {
165                     return false;
166                 }
167             } else {
168                 throw new IllegalReferenceCountException(realCnt, -decrement);
169             }
170             Thread.yield(); // this benefits throughput under high contention
171         }
172     }
173 
174     public enum UpdaterType {
175         Unsafe,
176         VarHandle,
177         Atomic
178     }
179 
180     public static <T extends ReferenceCounted> UpdaterType updaterTypeOf(Class<T> clz, String fieldName) {
181         long fieldOffset = getUnsafeOffset(clz, fieldName);
182         if (fieldOffset >= 0) {
183             return UpdaterType.Unsafe;
184         }
185         if (PlatformDependent.hasVarHandle()) {
186             return UpdaterType.VarHandle;
187         }
188         return UpdaterType.Atomic;
189     }
190 
191     public static long getUnsafeOffset(Class<? extends ReferenceCounted> clz, String fieldName) {
192         try {
193             if (PlatformDependent.hasUnsafe()) {
194                 return PlatformDependent.objectFieldOffset(clz.getDeclaredField(fieldName));
195             }
196         } catch (Throwable ignore) {
197             // fall-back
198         }
199         return -1;
200     }
201 }