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