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.internal.misc.ScopedMemoryAccess;
 29 import jdk.internal.vm.annotation.ForceInline;
 30 
 31 import java.lang.invoke.MethodHandles;
 32 import java.lang.invoke.VarHandle;
 33 import java.lang.ref.Cleaner;
 34 
 35 /**
 36  * A shared scope, which can be shared across multiple threads. Closing a shared scope has to ensure that
 37  * (i) only one thread can successfully close a scope (e.g. in a close vs. close race) and that
 38  * (ii) no other thread is accessing the memory associated with this scope while the segment is being
 39  * closed. To ensure the former condition, a CAS is performed on the liveness bit. Ensuring the latter
 40  * is trickier, and require a complex synchronization protocol (see {@link jdk.internal.misc.ScopedMemoryAccess}).
 41  * Since it is the responsibility of the closing thread to make sure that no concurrent access is possible,
 42  * checking the liveness bit upon access can be performed in plain mode, as in the confined case.
 43  */
 44 class SharedScope extends ResourceScopeImpl {
 45 
 46     private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
 47 
 48     private static final int ALIVE = 0;
 49     private static final int CLOSING = -1;
 50     private static final int CLOSED = -2;
 51 
 52     private int state = ALIVE;
 53 
 54     private static final VarHandle STATE;
 55 
 56     static {
 57         try {
 58             STATE = MethodHandles.lookup().findVarHandle(jdk.internal.foreign.SharedScope.class, "state", int.class);
 59         } catch (Throwable ex) {
 60             throw new ExceptionInInitializerError(ex);
 61         }
 62     }
 63 
 64     SharedScope(Cleaner cleaner) {
 65         super(new SharedResourceList(), cleaner);
 66     }
 67 
 68     @Override
 69     public Thread ownerThread() {
 70         return null;
 71     }
 72 
 73     @Override
 74     public void checkValidState() {
 75         if (state < ALIVE) {
 76             throw ScopedAccessError.INSTANCE;
 77         }
 78     }
 79 
 80     @Override
 81     @ForceInline
 82     public void acquire0() {
 83         int value;
 84         do {
 85             value = (int) STATE.getVolatile(this);
 86             if (value < ALIVE) {
 87                 //segment is not alive!
 88                 throw new IllegalStateException("Already closed");
 89             } else if (value == MAX_FORKS) {
 90                 //overflow
 91                 throw new IllegalStateException("Scope keep alive limit exceeded");
 92             }
 93         } while (!STATE.compareAndSet(this, value, value + 1));
 94     }
 95 
 96     @Override
 97     @ForceInline
 98     public void release0() {
 99         int value;
100         do {
101             value = (int) STATE.getVolatile(jdk.internal.foreign.SharedScope.this);
102             if (value <= ALIVE) {
103                 //cannot get here - we can't close segment twice
104                 throw new IllegalStateException("Already closed");
105             }
106         } while (!STATE.compareAndSet(jdk.internal.foreign.SharedScope.this, value, value - 1));
107     }
108 
109     void justClose() {
110         int prevState = (int) STATE.compareAndExchange(this, ALIVE, CLOSING);
111         if (prevState < 0) {
112             throw new IllegalStateException("Already closed");
113         } else if (prevState != ALIVE) {
114             throw new IllegalStateException("Scope is kept alive by " + prevState + " scopes");
115         }
116         boolean success = SCOPED_MEMORY_ACCESS.closeScope(this);
117         STATE.setVolatile(this, success ? CLOSED : ALIVE);
118         if (!success) {
119             throw new IllegalStateException("Cannot close while another thread is accessing the segment");
120         }
121     }
122 
123     @Override
124     public boolean isAlive() {
125         return (int) STATE.getVolatile(this) != CLOSED;
126     }
127 
128     /**
129      * A shared resource list; this implementation has to handle add vs. add races, as well as add vs. cleanup races.
130      */
131     static class SharedResourceList extends ResourceList {
132 
133         static final VarHandle FST;
134 
135         static {
136             try {
137                 FST = MethodHandles.lookup().findVarHandle(ResourceList.class, "fst", ResourceCleanup.class);
138             } catch (Throwable ex) {
139                 throw new ExceptionInInitializerError();
140             }
141         }
142 
143         @Override
144         void add(ResourceCleanup cleanup) {
145             while (true) {
146                 ResourceCleanup prev = (ResourceCleanup) FST.getAcquire(this);
147                 cleanup.next = prev;
148                 ResourceCleanup newSegment = (ResourceCleanup) FST.compareAndExchangeRelease(this, prev, cleanup);
149                 if (newSegment == ResourceCleanup.CLOSED_LIST) {
150                     // too late
151                     throw new IllegalStateException("Already closed");
152                 } else if (newSegment == prev) {
153                     return; //victory
154                 }
155                 // keep trying
156             }
157         }
158 
159         void cleanup() {
160             // At this point we are only interested about add vs. close races - not close vs. close
161             // (because MemoryScope::justClose ensured that this thread won the race to close the scope).
162             // So, the only "bad" thing that could happen is that some other thread adds to this list
163             // while we're closing it.
164             if (FST.getAcquire(this) != ResourceCleanup.CLOSED_LIST) {
165                 //ok now we're really closing down
166                 ResourceCleanup prev = null;
167                 while (true) {
168                     prev = (ResourceCleanup) FST.getAcquire(this);
169                     // no need to check for DUMMY, since only one thread can get here!
170                     if (FST.weakCompareAndSetRelease(this, prev, ResourceCleanup.CLOSED_LIST)) {
171                         break;
172                     }
173                 }
174                 cleanup(prev);
175             } else {
176                 throw new IllegalStateException("Attempt to cleanup an already closed resource list");
177             }
178         }
179     }
180 }