View Javadoc
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 }