1 /* 2 * Copyright 2021 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.netty5.buffer.api.internal; 17 18 import io.netty5.buffer.api.Drop; 19 import io.netty5.buffer.api.Owned; 20 import io.netty5.util.Resource; 21 import io.netty5.util.Send; 22 import io.netty5.util.internal.UnstableApi; 23 24 import java.util.Objects; 25 26 /** 27 * Internal support class for resources. 28 * 29 * @param <I> The public interface for the resource. 30 * @param <T> The concrete implementation of the resource. 31 */ 32 @UnstableApi 33 public abstract class ResourceSupport<I extends Resource<I>, T extends ResourceSupport<I, T>> implements Resource<I> { 34 private int acquires; // Closed if negative. 35 private Drop<T> drop; 36 private final LifecycleTracer tracer; 37 38 protected ResourceSupport(Drop<T> drop) { 39 this.drop = drop; 40 tracer = LifecycleTracer.get(); 41 tracer.allocate(); 42 } 43 44 /** 45 * Encapsulation bypass for calling {@link #acquire()} on the given object. 46 * <p> 47 * Note: this {@code acquire} method does not check the type of the return value from acquire at compile time. 48 * The type is instead checked at runtime, and will cause a {@link ClassCastException} to be thrown if done 49 * incorrectly. 50 * 51 * @param obj The object we wish to acquire (increment reference count) on. 52 * @param <T> The type of the acquired object, given by target-typing. 53 * @return The acquired object. 54 */ 55 @SuppressWarnings("unchecked") 56 static <T> T acquire(ResourceSupport<?, ?> obj) { 57 return (T) obj.acquire(); 58 } 59 60 /** 61 * Encapsulation bypass for getting the {@link LifecycleTracer} attached to the given object. 62 * 63 * @param obj The object to get the {@link LifecycleTracer} from. 64 * @return The {@link LifecycleTracer} that is attached to the given object. 65 */ 66 static LifecycleTracer getTracer(ResourceSupport<?, ?> obj) { 67 return obj.tracer; 68 } 69 70 /** 71 * Increment the reference count. 72 * <p> 73 * Note, this method is not thread-safe because Resources are meant to thread-confined. 74 * 75 * @return This {@link Resource} instance. 76 */ 77 protected final I acquire() { 78 if (acquires < 0) { 79 throw attachTrace(createResourceClosedException()); 80 } 81 if (acquires == Integer.MAX_VALUE) { 82 throw new IllegalStateException("Reached maximum allowed acquires (" + Integer.MAX_VALUE + ")."); 83 } 84 acquires++; 85 tracer.acquire(acquires); 86 return self(); 87 } 88 89 protected abstract RuntimeException createResourceClosedException(); 90 91 /** 92 * Decrement the reference count, and dispose of the resource if the last reference is closed. 93 * <p> 94 * Note, this method is not thread-safe because Resources are meant to be thread-confined. 95 * <p> 96 * Subclasses who wish to attach behaviour to the close action should override the {@link #makeInaccessible()} 97 * method instead, or make it part of their drop implementation. 98 * 99 * @throws IllegalStateException If this Resource has already been closed. 100 */ 101 @Override 102 public final void close() { 103 if (acquires == -1) { 104 throw attachTrace(new IllegalStateException("Double-free: Resource already closed and dropped.")); 105 } 106 int acq = acquires; 107 acquires--; 108 if (acq != 0) { 109 // Only record a CLOSE if we're not going to record a DROP. 110 tracer.close(acq); 111 } else { 112 // The 'acquires' was 0, now decremented to -1, which means we need to drop. 113 tracer.drop(0); 114 try { 115 drop.drop(impl()); 116 } finally { 117 makeInaccessible(); 118 } 119 } 120 } 121 122 /** 123 * Send this Resource instance to another Thread, transferring the ownership to the recipient. 124 * This method can be used when the receiving thread is not known up front. 125 * <p> 126 * This instance immediately becomes inaccessible, and all attempts at accessing this resource will throw. 127 * Calling {@link #close()} will have no effect, so this method is safe to call within a try-with-resources 128 * statement. 129 * 130 * @throws IllegalStateException if this object has any outstanding acquires; that is, if this object has been 131 * {@link #acquire() acquired} more times than it has been {@link #close() closed}. 132 */ 133 @Override 134 public final Send<I> send() { 135 if (acquires < 0) { 136 throw attachTrace(createResourceClosedException()); 137 } 138 if (!isOwned()) { 139 throw notSendableException(); 140 } 141 try { 142 var owned = tracer.send(prepareSend()); 143 return new SendFromOwned<>(owned, drop, getClass()); 144 } finally { 145 acquires = -2; // Close without dropping. This also ignore future double-free attempts. 146 makeInaccessible(); 147 } 148 } 149 150 /** 151 * Attach a trace of the life-cycle of this object as suppressed exceptions to the given throwable. 152 * 153 * @param throwable The throwable to attach a life-cycle trace to. 154 * @param <E> The concrete exception type. 155 * @return The given exception, which can then be thrown. 156 */ 157 protected <E extends Throwable> E attachTrace(E throwable) { 158 return tracer.attachTrace(throwable); 159 } 160 161 /** 162 * Create an {@link IllegalStateException} with a custom message, tailored to this particular 163 * {@link Resource} instance, for when the object cannot be sent for some reason. 164 * @return An {@link IllegalStateException} to be thrown when this object cannot be sent. 165 */ 166 protected IllegalStateException notSendableException() { 167 return new IllegalStateException( 168 "Cannot send() a reference counted object with " + countBorrows() + " borrows: " + this + '.'); 169 } 170 171 /** 172 * Encapsulation bypass to call {@link #isOwned()} on the given object. 173 * 174 * @param obj The object to query the ownership state on. 175 * @return {@code true} if the given object is owned, otherwise {@code false}. 176 */ 177 static boolean isOwned(ResourceSupport<?, ?> obj) { 178 return obj.isOwned(); 179 } 180 181 /** 182 * Query if this object is in an "owned" state, which means no other references have been 183 * {@linkplain #acquire() acquired} to it. 184 * 185 * This would usually be the case, since there are no public methods for acquiring references to these objects. 186 * 187 * @return {@code true} if this object is in an owned state, otherwise {@code false}. 188 */ 189 protected boolean isOwned() { 190 return acquires == 0; 191 } 192 193 /** 194 * Encapsulation bypass to call {@link #countBorrows()} on the given object. 195 * 196 * @param obj The object to count borrows on. 197 * @return The number of borrows, or outstanding {@linkplain #acquire() acquires}, if any, of the given object. 198 */ 199 static int countBorrows(ResourceSupport<?, ?> obj) { 200 return obj.countBorrows(); 201 } 202 203 /** 204 * Count the number of borrows of this object. 205 * Note that even if the number of borrows is {@code 0}, this object might not be {@linkplain #isOwned() owned} 206 * because there could be other restrictions involved in ownership. 207 * 208 * @return The number of borrows, if any, of this object. 209 */ 210 protected int countBorrows() { 211 return Math.max(acquires, 0); 212 } 213 214 @Override 215 public boolean isAccessible() { 216 return acquires >= 0; 217 } 218 219 @Override 220 public I touch(Object hint) { 221 if (isAccessible()) { 222 tracer.touch(hint); 223 } 224 return self(); 225 } 226 227 /** 228 * Prepare this instance for ownership transfer. This method is called from {@link #send()} in the sending thread. 229 * This method should put this resource in a deactivated state where it is no longer accessible from the currently 230 * owning thread. 231 * In this state, the resource instance should only allow a call to {@link Owned#transferOwnership(Drop)} in the 232 * recipient thread. 233 * 234 * @return This resource instance in a deactivated state. 235 */ 236 protected abstract Owned<T> prepareSend(); 237 238 /** 239 * Called when this resource needs to be considered inaccessible. 240 * This is called at the correct points, by the {@link ResourceSupport} class, 241 * when the resource is being closed or sent, 242 * and can be used to set further traps for accesses that makes accessibility checks cheap. 243 * There would normally not be any reason to call this directly from a sub-class. 244 */ 245 protected void makeInaccessible() { 246 } 247 248 /** 249 * Get access to the underlying {@link Drop} object. 250 * This method is unsafe because it opens the possibility of bypassing and overriding resource lifetimes. 251 * 252 * @return The {@link Drop} object used by this reference counted object. 253 */ 254 protected Drop<T> unsafeGetDrop() { 255 return drop; 256 } 257 258 /** 259 * Replace the current underlying {@link Drop} object with the given one. 260 * This method is unsafe because it opens the possibility of bypassing and overriding resource lifetimes. 261 * 262 * @param replacement The new {@link Drop} object to use instead of the current one. 263 */ 264 protected void unsafeSetDrop(Drop<T> replacement) { 265 drop = Objects.requireNonNull(replacement, "Replacement drop cannot be null."); 266 } 267 268 @SuppressWarnings("unchecked") 269 private I self() { 270 return (I) this; 271 } 272 273 @SuppressWarnings("unchecked") 274 private T impl() { 275 return (T) this; 276 } 277 }