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 }