1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package libcore.util;
18
19import dalvik.system.VMRuntime;
20import sun.misc.Cleaner;
21
22/**
23 * A NativeAllocationRegistry is used to associate native allocations with
24 * Java objects and register them with the runtime.
25 * There are two primary benefits of registering native allocations associated
26 * with Java objects:
27 * <ol>
28 *  <li>The runtime will account for the native allocations when scheduling
29 *  garbage collection to run.</li>
30 *  <li>The runtime will arrange for the native allocation to be automatically
31 *  freed by a user-supplied function when the associated Java object becomes
32 *  unreachable.</li>
33 * </ol>
34 * A separate NativeAllocationRegistry should be instantiated for each kind
35 * of native allocation, where the kind of a native allocation consists of the
36 * native function used to free the allocation and the estimated size of the
37 * allocation. Once a NativeAllocationRegistry is instantiated, it can be
38 * used to register any number of native allocations of that kind.
39 * @hide
40 */
41public class NativeAllocationRegistry {
42
43    private final ClassLoader classLoader;
44    private final long freeFunction;
45    private final long size;
46
47    /**
48     * Constructs a NativeAllocationRegistry for a particular kind of native
49     * allocation.
50     * The address of a native function that can be used to free this kind
51     * native allocation should be provided using the
52     * <code>freeFunction</code> argument. The native function should have the
53     * type:
54     * <pre>
55     *    void f(void* nativePtr);
56     * </pre>
57     * <p>
58     * The <code>classLoader</code> argument should be the class loader used
59     * to load the native library that freeFunction belongs to. This is needed
60     * to ensure the native library doesn't get unloaded before freeFunction
61     * is called.
62     * <p>
63     * The <code>size</code> should be an estimate of the total number of
64     * native bytes this kind of native allocation takes up. Different
65     * NativeAllocationRegistrys must be used to register native allocations
66     * with different estimated sizes, even if they use the same
67     * <code>freeFunction</code>.
68     * @param classLoader  ClassLoader that was used to load the native
69     *                     library freeFunction belongs to.
70     * @param freeFunction address of a native function used to free this
71     *                     kind of native allocation
72     * @param size         estimated size in bytes of this kind of native
73     *                     allocation
74     * @throws IllegalArgumentException If <code>size</code> is negative
75     */
76    public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
77        if (size < 0) {
78            throw new IllegalArgumentException("Invalid native allocation size: " + size);
79        }
80
81        this.classLoader = classLoader;
82        this.freeFunction = freeFunction;
83        this.size = size;
84    }
85
86    /**
87     * Registers a new native allocation and associated Java object with the
88     * runtime.
89     * This NativeAllocationRegistry's <code>freeFunction</code> will
90     * automatically be called with <code>nativePtr</code> as its sole
91     * argument when <code>referent</code> becomes unreachable. If you
92     * maintain copies of <code>nativePtr</code> outside
93     * <code>referent</code>, you must not access these after
94     * <code>referent</code> becomes unreachable, because they may be dangling
95     * pointers.
96     * <p>
97     * The returned Runnable can be used to free the native allocation before
98     * <code>referent</code> becomes unreachable. The runnable will have no
99     * effect if the native allocation has already been freed by the runtime
100     * or by using the runnable.
101     * <p>
102     * WARNING: This unconditionally takes ownership, i.e. deallocation
103     * responsibility of nativePtr. nativePtr will be DEALLOCATED IMMEDIATELY
104     * if the registration attempt throws an exception (other than one reporting
105     * a programming error).
106     *
107     * @param referent      Non-null java object to associate the native allocation with
108     * @param nativePtr     Non-zero address of the native allocation
109     * @return runnable to explicitly free native allocation
110     * @throws IllegalArgumentException if either referent or nativePtr is null.
111     * @throws OutOfMemoryError  if there is not enough space on the Java heap
112     *                           in which to register the allocation. In this
113     *                           case, <code>freeFunction</code> will be
114     *                           called with <code>nativePtr</code> as its
115     *                           argument before the OutOfMemoryError is
116     *                           thrown.
117     */
118    public Runnable registerNativeAllocation(Object referent, long nativePtr) {
119        if (referent == null) {
120            throw new IllegalArgumentException("referent is null");
121        }
122        if (nativePtr == 0) {
123            throw new IllegalArgumentException("nativePtr is null");
124        }
125
126        CleanerThunk thunk;
127        CleanerRunner result;
128        try {
129            thunk = new CleanerThunk();
130            Cleaner cleaner = Cleaner.create(referent, thunk);
131            result = new CleanerRunner(cleaner);
132            registerNativeAllocation(this.size);
133        } catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
134            applyFreeFunction(freeFunction, nativePtr);
135            throw vme;
136        } // Other exceptions are impossible.
137        // Enable the cleaner only after we can no longer throw anything, including OOME.
138        thunk.setNativePtr(nativePtr);
139        return result;
140    }
141
142    /**
143     * Interface for custom native allocation allocators used by
144     * {@link #registerNativeAllocation(Object, Allocator) registerNativeAllocation(Object, Allocator)}.
145     */
146    public interface Allocator {
147        /**
148         * Allocate a native allocation and return its address.
149         */
150        long allocate();
151    }
152
153    /**
154     * Registers and allocates a new native allocation and associated Java
155     * object with the runtime.
156     * This can be used for registering large allocations where the underlying
157     * native allocation shouldn't be performed until it's clear there is
158     * enough space on the Java heap to register the allocation.
159     * <p>
160     * If the allocator returns null, the allocation is not registered and a
161     * null Runnable is returned.
162     *
163     * @param referent      Non-null java object to associate the native allocation with
164     * @param allocator     used to perform the underlying native allocation.
165     * @return runnable to explicitly free native allocation
166     * @throws IllegalArgumentException if referent is null.
167     * @throws OutOfMemoryError  if there is not enough space on the Java heap
168     *                           in which to register the allocation. In this
169     *                           case, the allocator will not be run.
170     */
171    public Runnable registerNativeAllocation(Object referent, Allocator allocator) {
172        if (referent == null) {
173            throw new IllegalArgumentException("referent is null");
174        }
175
176        // Create the cleaner before running the allocator so that
177        // VMRuntime.registerNativeFree is eventually called if the allocate
178        // method throws an exception.
179        CleanerThunk thunk = new CleanerThunk();
180        Cleaner cleaner = Cleaner.create(referent, thunk);
181        CleanerRunner result = new CleanerRunner(cleaner);
182        long nativePtr = allocator.allocate();
183        if (nativePtr == 0) {
184            cleaner.clean();
185            return null;
186        }
187        registerNativeAllocation(this.size);
188        thunk.setNativePtr(nativePtr);
189        return result;
190    }
191
192    private class CleanerThunk implements Runnable {
193        private long nativePtr;
194
195        public CleanerThunk() {
196            this.nativePtr = 0;
197        }
198
199        public void run() {
200            if (nativePtr != 0) {
201                applyFreeFunction(freeFunction, nativePtr);
202                registerNativeFree(size);
203            }
204        }
205
206        public void setNativePtr(long nativePtr) {
207            this.nativePtr = nativePtr;
208        }
209    }
210
211    private static class CleanerRunner implements Runnable {
212        private final Cleaner cleaner;
213
214        public CleanerRunner(Cleaner cleaner) {
215            this.cleaner = cleaner;
216        }
217
218        public void run() {
219            cleaner.clean();
220        }
221    }
222
223    // TODO: Change the runtime to support passing the size as a long instead
224    // of an int. For now, we clamp the size to fit.
225    private static void registerNativeAllocation(long size) {
226        VMRuntime.getRuntime().registerNativeAllocation((int)Math.min(size, Integer.MAX_VALUE));
227    }
228
229    private static void registerNativeFree(long size) {
230        VMRuntime.getRuntime().registerNativeFree((int)Math.min(size, Integer.MAX_VALUE));
231    }
232
233    /**
234     * Calls <code>freeFunction</code>(<code>nativePtr</code>).
235     * Provided as a convenience in the case where you wish to manually free a
236     * native allocation using a <code>freeFunction</code> without using a
237     * NativeAllocationRegistry.
238     */
239    public static native void applyFreeFunction(long freeFunction, long nativePtr);
240}
241
242