1 /*
  2  * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.internal.foreign;
 27 
 28 import jdk.incubator.foreign.ResourceScope;
 29 import jdk.internal.misc.ScopedMemoryAccess;
 30 
 31 import java.lang.invoke.MethodHandles;
 32 import java.lang.invoke.VarHandle;
 33 import java.lang.ref.Cleaner;
 34 import java.lang.ref.Reference;
 35 import java.util.concurrent.atomic.AtomicBoolean;
 36 
 37 /**
 38  * A shared scope, which can be shared across multiple threads. Closing a shared scope has to ensure that
 39  * (i) only one thread can successfully close a scope (e.g. in a close vs. close race) and that
 40  * (ii) no other thread is accessing the memory associated with this scope while the segment is being
 41  * closed. To ensure the former condition, a CAS is performed on the liveness bit. Ensuring the latter
 42  * is trickier, and require a complex synchronization protocol (see {@link jdk.internal.misc.ScopedMemoryAccess}).
 43  * Since it is the responsibility of the closing thread to make sure that no concurrent access is possible,
 44  * checking the liveness bit upon access can be performed in plain mode, as in the confined case.
 45  */
 46 class SharedScope extends ResourceScopeImpl {
 47 
 48     private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
 49 
 50     private static final int ALIVE = 0;
 51     private static final int CLOSING = -1;
 52     private static final int CLOSED = -2;
 53     private static final int MAX_FORKS = Integer.MAX_VALUE;
 54 
 55     private int state = ALIVE;
 56 
 57     private static final VarHandle STATE;
 58 
 59     static {
 60         try {
 61             STATE = MethodHandles.lookup().findVarHandle(jdk.internal.foreign.SharedScope.class, "state", int.class);
 62         } catch (Throwable ex) {
 63             throw new ExceptionInInitializerError(ex);
 64         }
 65     }
 66 
 67     SharedScope(Cleaner cleaner) {
 68         super(cleaner, new SharedResourceList());
 69     }
 70 
 71     @Override
 72     public Thread ownerThread() {
 73         return null;
 74     }
 75 
 76     @Override
 77     public void checkValidState() {
 78         if (state < ALIVE) {
 79             throw ScopedAccessError.INSTANCE;
 80         }
 81     }
 82 
 83     @Override
 84     public HandleImpl acquire() {
 85         int value;
 86         do {
 87             value = (int) STATE.getVolatile(this);
 88             if (value < ALIVE) {
 89                 //segment is not alive!
 90                 throw new IllegalStateException("Already closed");
 91             } else if (value == MAX_FORKS) {
 92                 //overflow
 93                 throw new IllegalStateException("Segment acquire limit exceeded");
 94             }
 95         } while (!STATE.compareAndSet(this, value, value + 1));
 96         return new SharedHandle();
 97     }
 98 
 99     void justClose() {
100         int prevState = (int) STATE.compareAndExchange(this, ALIVE, CLOSING);
101         if (prevState < 0) {
102             throw new IllegalStateException("Already closed");
103         } else if (prevState != ALIVE) {
104             throw new IllegalStateException("Scope is acquired by " + prevState + " locks");
105         }
106         boolean success = SCOPED_MEMORY_ACCESS.closeScope(this);
107         STATE.setVolatile(this, success ? CLOSED : ALIVE);
108         if (!success) {
109             throw new IllegalStateException("Cannot close while another thread is accessing the segment");
110         }
111     }
112 
113     @Override
114     public boolean isAlive() {
115         return (int) STATE.getVolatile(this) != CLOSED;
116     }
117 
118     /**
119      * A shared resource list; this implementation has to handle add vs. add races, as well as add vs. cleanup races.
120      */
121     static class SharedResourceList extends ResourceList {
122 
123         static final VarHandle FST;
124 
125         static {
126             try {
127                 FST = MethodHandles.lookup().findVarHandle(ResourceList.class, "fst", ResourceCleanup.class);
128             } catch (Throwable ex) {
129                 throw new ExceptionInInitializerError();
130             }
131         }
132 
133         @Override
134         void add(ResourceCleanup cleanup) {
135             while (true) {
136                 ResourceCleanup prev = (ResourceCleanup) FST.getAcquire(this);
137                 cleanup.next = prev;
138                 ResourceCleanup newSegment = (ResourceCleanup) FST.compareAndExchangeRelease(this, prev, cleanup);
139                 if (newSegment == ResourceCleanup.CLOSED_LIST) {
140                     // too late
141                     throw new IllegalStateException("Already closed");
142                 } else if (newSegment == prev) {
143                     return; //victory
144                 }
145                 // keep trying
146             }
147         }
148 
149         void cleanup() {
150             // At this point we are only interested about add vs. close races - not close vs. close
151             // (because MemoryScope::justClose ensured that this thread won the race to close the scope).
152             // So, the only "bad" thing that could happen is that some other thread adds to this list
153             // while we're closing it.
154             if (FST.getAcquire(this) != ResourceCleanup.CLOSED_LIST) {
155                 //ok now we're really closing down
156                 ResourceCleanup prev = null;
157                 while (true) {
158                     prev = (ResourceCleanup) FST.getAcquire(this);
159                     // no need to check for DUMMY, since only one thread can get here!
160                     if (FST.weakCompareAndSetRelease(this, prev, ResourceCleanup.CLOSED_LIST)) {
161                         break;
162                     }
163                 }
164                 cleanup(prev);
165             } else {
166                 throw new IllegalStateException("Attempt to cleanup an already closed resource list");
167             }
168         }
169     }
170 
171     /**
172      * A shared resource scope handle; this implementation has to handle close vs. close races.
173      */
174     class SharedHandle implements HandleImpl {
175         final AtomicBoolean released = new AtomicBoolean(false);
176 
177         @Override
178         public ResourceScopeImpl scope() {
179             return SharedScope.this;
180         }
181 
182         @Override
183         public void release() {
184             if (released.compareAndSet(false, true)) {
185                 int value;
186                 do {
187                     value = (int) STATE.getVolatile(jdk.internal.foreign.SharedScope.this);
188                     if (value <= ALIVE) {
189                         //cannot get here - we can't close segment twice
190                         throw new IllegalStateException("Already closed");
191                     }
192                 } while (!STATE.compareAndSet(jdk.internal.foreign.SharedScope.this, value, value - 1));
193             }
194         }
195     }
196 }