Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 | |
| 17 | package android.util; |
| 18 | |
| 19 | import android.os.Parcel; |
| 20 | import android.os.ParcelFileDescriptor; |
| 21 | import android.os.Parcelable; |
Svet Ganov | 9d723d3 | 2016-08-27 11:05:56 -0700 | [diff] [blame] | 22 | |
Josh Gao | c81f53f | 2018-08-09 15:09:02 -0700 | [diff] [blame] | 23 | import libcore.io.IoUtils; |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 24 | import dalvik.system.CloseGuard; |
Josh Gao | c81f53f | 2018-08-09 15:09:02 -0700 | [diff] [blame] | 25 | |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 26 | import java.io.Closeable; |
| 27 | import java.io.IOException; |
| 28 | import java.util.UUID; |
| 29 | |
| 30 | /** |
| 31 | * This class is an array of integers that is backed by shared memory. |
| 32 | * It is useful for efficiently sharing state between processes. The |
| 33 | * write and read operations are guaranteed to not result in read/ |
| 34 | * write memory tear, i.e. they are atomic. However, multiple read/ |
| 35 | * write operations are <strong>not</strong> synchronized between |
| 36 | * each other. |
| 37 | * <p> |
| 38 | * The data structure is designed to have one owner process that can |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 39 | * read/write. There may be multiple client processes that can only read. |
| 40 | * The owner process is the process that created the array. The shared |
| 41 | * memory is pinned (not reclaimed by the system) until the owning process |
| 42 | * dies or the data structure is closed. This class is <strong>not</strong> |
| 43 | * thread safe. You should not interact with an instance of this class |
| 44 | * once it is closed. If you pass back to the owner process an instance |
| 45 | * it will be read only even in the owning process. |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 46 | * </p> |
| 47 | * |
| 48 | * @hide |
| 49 | */ |
| 50 | public final class MemoryIntArray implements Parcelable, Closeable { |
Svetoslav Ganov | 04df738 | 2016-05-10 18:55:47 -0700 | [diff] [blame] | 51 | private static final String TAG = "MemoryIntArray"; |
| 52 | |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 53 | private static final int MAX_SIZE = 1024; |
| 54 | |
Svet Ganov | 9d723d3 | 2016-08-27 11:05:56 -0700 | [diff] [blame] | 55 | private final CloseGuard mCloseGuard = CloseGuard.get(); |
| 56 | |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 57 | private final boolean mIsOwner; |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 58 | private final long mMemoryAddr; |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 59 | private int mFd = -1; |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 60 | |
| 61 | /** |
| 62 | * Creates a new instance. |
| 63 | * |
| 64 | * @param size The size of the array in terms of integer slots. Cannot be |
| 65 | * more than {@link #getMaxSize()}. |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 66 | * @throws IOException If an error occurs while accessing the shared memory. |
| 67 | */ |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 68 | public MemoryIntArray(int size) throws IOException { |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 69 | if (size > MAX_SIZE) { |
| 70 | throw new IllegalArgumentException("Max size is " + MAX_SIZE); |
| 71 | } |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 72 | mIsOwner = true; |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 73 | final String name = UUID.randomUUID().toString(); |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 74 | mFd = nativeCreate(name, size); |
| 75 | mMemoryAddr = nativeOpen(mFd, mIsOwner); |
Svet Ganov | 9d723d3 | 2016-08-27 11:05:56 -0700 | [diff] [blame] | 76 | mCloseGuard.open("close"); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 77 | } |
| 78 | |
| 79 | private MemoryIntArray(Parcel parcel) throws IOException { |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 80 | mIsOwner = false; |
Svetoslav Ganov | fe2462f | 2016-08-29 11:14:05 -0700 | [diff] [blame] | 81 | ParcelFileDescriptor pfd = parcel.readParcelable(null); |
| 82 | if (pfd == null) { |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 83 | throw new IOException("No backing file descriptor"); |
| 84 | } |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 85 | mFd = pfd.detachFd(); |
| 86 | mMemoryAddr = nativeOpen(mFd, mIsOwner); |
Svet Ganov | 9d723d3 | 2016-08-27 11:05:56 -0700 | [diff] [blame] | 87 | mCloseGuard.open("close"); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 88 | } |
| 89 | |
| 90 | /** |
| 91 | * @return Gets whether this array is mutable. |
| 92 | */ |
| 93 | public boolean isWritable() { |
| 94 | enforceNotClosed(); |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 95 | return mIsOwner; |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Gets the value at a given index. |
| 100 | * |
| 101 | * @param index The index. |
| 102 | * @return The value at this index. |
| 103 | * @throws IOException If an error occurs while accessing the shared memory. |
| 104 | */ |
| 105 | public int get(int index) throws IOException { |
| 106 | enforceNotClosed(); |
| 107 | enforceValidIndex(index); |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 108 | return nativeGet(mFd, mMemoryAddr, index); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Sets the value at a given index. This method can be called only if |
| 113 | * {@link #isWritable()} returns true which means your process is the |
| 114 | * owner. |
| 115 | * |
| 116 | * @param index The index. |
| 117 | * @param value The value to set. |
| 118 | * @throws IOException If an error occurs while accessing the shared memory. |
| 119 | */ |
| 120 | public void set(int index, int value) throws IOException { |
| 121 | enforceNotClosed(); |
| 122 | enforceWritable(); |
| 123 | enforceValidIndex(index); |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 124 | nativeSet(mFd, mMemoryAddr, index, value); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Gets the array size. |
| 129 | * |
| 130 | * @throws IOException If an error occurs while accessing the shared memory. |
| 131 | */ |
| 132 | public int size() throws IOException { |
| 133 | enforceNotClosed(); |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 134 | return nativeSize(mFd); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 135 | } |
| 136 | |
| 137 | /** |
| 138 | * Closes the array releasing resources. |
| 139 | * |
| 140 | * @throws IOException If an error occurs while accessing the shared memory. |
| 141 | */ |
| 142 | @Override |
| 143 | public void close() throws IOException { |
| 144 | if (!isClosed()) { |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 145 | nativeClose(mFd, mMemoryAddr, mIsOwner); |
| 146 | mFd = -1; |
Svet Ganov | 9d723d3 | 2016-08-27 11:05:56 -0700 | [diff] [blame] | 147 | mCloseGuard.close(); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 148 | } |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * @return Whether this array is closed and shouldn't be used. |
| 153 | */ |
| 154 | public boolean isClosed() { |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 155 | return mFd == -1; |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 156 | } |
| 157 | |
| 158 | @Override |
| 159 | protected void finalize() throws Throwable { |
Svet Ganov | 9d723d3 | 2016-08-27 11:05:56 -0700 | [diff] [blame] | 160 | try { |
Narayan Kamath | 492e9e8 | 2017-03-22 14:28:08 +0000 | [diff] [blame] | 161 | if (mCloseGuard != null) { |
| 162 | mCloseGuard.warnIfOpen(); |
| 163 | } |
| 164 | |
Svet Ganov | 9d723d3 | 2016-08-27 11:05:56 -0700 | [diff] [blame] | 165 | IoUtils.closeQuietly(this); |
| 166 | } finally { |
| 167 | super.finalize(); |
| 168 | } |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 169 | } |
| 170 | |
| 171 | @Override |
| 172 | public int describeContents() { |
| 173 | return CONTENTS_FILE_DESCRIPTOR; |
| 174 | } |
| 175 | |
| 176 | @Override |
| 177 | public void writeToParcel(Parcel parcel, int flags) { |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 178 | ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(mFd); |
| 179 | try { |
| 180 | // Don't let writing to a parcel to close our fd - plz |
| 181 | parcel.writeParcelable(pfd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE); |
| 182 | } finally { |
| 183 | pfd.detachFd(); |
| 184 | } |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 185 | } |
| 186 | |
| 187 | @Override |
| 188 | public boolean equals(Object obj) { |
| 189 | if (obj == null) { |
| 190 | return false; |
| 191 | } |
| 192 | if (this == obj) { |
| 193 | return true; |
| 194 | } |
| 195 | if (getClass() != obj.getClass()) { |
| 196 | return false; |
| 197 | } |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 198 | MemoryIntArray other = (MemoryIntArray) obj; |
| 199 | return mFd == other.mFd; |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 200 | } |
| 201 | |
| 202 | @Override |
| 203 | public int hashCode() { |
Svet Ganov | d240440 | 2019-05-03 19:17:49 -0700 | [diff] [blame] | 204 | return mFd; |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 205 | } |
| 206 | |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 207 | private void enforceNotClosed() { |
| 208 | if (isClosed()) { |
| 209 | throw new IllegalStateException("cannot interact with a closed instance"); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | private void enforceValidIndex(int index) throws IOException { |
| 214 | final int size = size(); |
| 215 | if (index < 0 || index > size - 1) { |
| 216 | throw new IndexOutOfBoundsException( |
| 217 | index + " not between 0 and " + (size - 1)); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | private void enforceWritable() { |
| 222 | if (!isWritable()) { |
| 223 | throw new UnsupportedOperationException("array is not writable"); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | private native int nativeCreate(String name, int size); |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 228 | private native long nativeOpen(int fd, boolean owner); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 229 | private native void nativeClose(int fd, long memoryAddr, boolean owner); |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 230 | private native int nativeGet(int fd, long memoryAddr, int index); |
| 231 | private native void nativeSet(int fd, long memoryAddr, int index, int value); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 232 | private native int nativeSize(int fd); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 233 | |
| 234 | /** |
| 235 | * @return The max array size. |
| 236 | */ |
| 237 | public static int getMaxSize() { |
| 238 | return MAX_SIZE; |
| 239 | } |
| 240 | |
Jeff Sharkey | 9e8f83d | 2019-02-28 12:06:45 -0700 | [diff] [blame] | 241 | public static final @android.annotation.NonNull Parcelable.Creator<MemoryIntArray> CREATOR = |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 242 | new Parcelable.Creator<MemoryIntArray>() { |
| 243 | @Override |
| 244 | public MemoryIntArray createFromParcel(Parcel parcel) { |
| 245 | try { |
| 246 | return new MemoryIntArray(parcel); |
| 247 | } catch (IOException ioe) { |
Svetoslav Ganov | 74c9983 | 2016-12-05 20:07:20 -0800 | [diff] [blame] | 248 | throw new IllegalArgumentException("Error unparceling MemoryIntArray"); |
Svet Ganov | 53a441c | 2016-04-19 19:38:00 -0700 | [diff] [blame] | 249 | } |
| 250 | } |
| 251 | |
| 252 | @Override |
| 253 | public MemoryIntArray[] newArray(int size) { |
| 254 | return new MemoryIntArray[size]; |
| 255 | } |
| 256 | }; |
| 257 | } |