001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.page; 018 019import java.io.Serializable; 020import java.time.Duration; 021 022import org.apache.wicket.pageStore.IPageStore; 023import org.apache.wicket.util.lang.Args; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027/** 028 * Synchronizes access to page instances from multiple threads 029 * 030 * @author Igor Vaynberg (ivaynberg) 031 */ 032public class PageAccessSynchronizer implements Serializable 033{ 034 private static final long serialVersionUID = 1L; 035 036 private static final Logger logger = LoggerFactory.getLogger(PageAccessSynchronizer.class); 037 038 /** lock manager responsible for locking and unlocking page instances */ 039 private final IPageLockManager pageLockManager; 040 041 /** 042 * Constructor 043 * 044 * @param timeout 045 * timeout value for acquiring a page lock 046 */ 047 public PageAccessSynchronizer(Duration timeout) 048 { 049 this(new DefaultPageLockManager(timeout)); 050 } 051 052 /** 053 * Constructor 054 * 055 * @param pageLockManager the lock manager 056 */ 057 public PageAccessSynchronizer(IPageLockManager pageLockManager) 058 { 059 this.pageLockManager = Args.notNull(pageLockManager, "pageLockManager"); 060 } 061 062 /** 063 * Acquire a lock to a page 064 * 065 * @param pageId 066 * page id 067 * @throws CouldNotLockPageException 068 * if lock could not be acquired 069 */ 070 public void lockPage(int pageId) throws CouldNotLockPageException 071 { 072 pageLockManager.lockPage(pageId); 073 } 074 075 /** 076 * Unlocks all pages locked by this thread 077 */ 078 public void unlockAllPages() 079 { 080 pageLockManager.unlockAllPages(); 081 } 082 083 /** 084 * Unlocks a single page locked by the current thread. 085 * 086 * @param pageId 087 * the id of the page which should be unlocked. 088 */ 089 public void unlockPage(int pageId) 090 { 091 pageLockManager.unlockPage(pageId); 092 } 093 094 /** 095 * Wraps a page manager with this synchronizer 096 * 097 * @param manager 098 * @return wrapped page manager 099 */ 100 public IPageManager adapt(final IPageManager manager) 101 { 102 return new IPageManager() 103 { 104 @Override 105 public boolean supportsVersioning() 106 { 107 return manager.supportsVersioning(); 108 } 109 110 @Override 111 public IManageablePage getPage(int pageId) 112 { 113 IManageablePage page = null; 114 try 115 { 116 lockPage(pageId); 117 page = manager.getPage(pageId); 118 } 119 finally 120 { 121 if (page == null) 122 { 123 unlockPage(pageId); 124 } 125 } 126 return page; 127 } 128 129 @Override 130 public void removePage(IManageablePage page) 131 { 132 if (page != null) 133 { 134 try 135 { 136 manager.removePage(page); 137 } 138 finally 139 { 140 unlockPage(page.getPageId()); 141 } 142 } 143 } 144 145 @Override 146 public void touchPage(IManageablePage page) 147 { 148 lockPage(page.getPageId()); 149 150 manager.touchPage(page); 151 } 152 153 @Override 154 public void clear() 155 { 156 manager.clear(); 157 } 158 159 @Override 160 public void untouchPage(IManageablePage page) 161 { 162 manager.untouchPage(page); 163 } 164 165 @Override 166 public void detach() 167 { 168 try 169 { 170 manager.detach(); 171 } 172 finally 173 { 174 unlockAllPages(); 175 } 176 } 177 178 @Override 179 public IPageStore getPageStore() 180 { 181 return manager.getPageStore(); 182 } 183 184 @Override 185 public void destroy() 186 { 187 manager.destroy(); 188 } 189 }; 190 } 191 192 /** 193 * Thread's lock on a page 194 * 195 * @author igor 196 */ 197 public static class PageLock 198 { 199 /** page id */ 200 private final int pageId; 201 202 /** thread that owns the lock */ 203 private final Thread thread; 204 205 private volatile boolean released = false; 206 207 /** 208 * Constructor 209 * 210 * @param pageId 211 * @param thread 212 */ 213 public PageLock(int pageId, Thread thread) 214 { 215 this.pageId = pageId; 216 this.thread = thread; 217 } 218 219 /** 220 * @return page id of locked page 221 */ 222 public int getPageId() 223 { 224 return pageId; 225 } 226 227 /** 228 * @return thread that owns the lock 229 */ 230 public Thread getThread() 231 { 232 return thread; 233 } 234 235 public final synchronized void waitForRelease(long remaining, boolean isDebugEnabled) 236 { 237 if (released) 238 { 239 // the thread holding the lock released it before we were able to wait for the 240 // release 241 if (isDebugEnabled) 242 { 243 logger.debug( 244 "lock for page with id {} no longer locked by {}, falling through", pageId, 245 thread.getName()); 246 } 247 return; 248 } 249 250 if (isDebugEnabled) 251 { 252 logger.debug("{} waiting for lock to page {} for {}", 253 thread.getName(), pageId, Duration.ofMillis(remaining)); 254 } 255 try 256 { 257 wait(remaining); 258 } 259 catch (InterruptedException e) 260 { 261 throw new RuntimeException(e); 262 } 263 } 264 265 public final synchronized void markReleased(boolean isDebugEnabled) 266 { 267 if (isDebugEnabled) 268 { 269 logger.debug("'{}' notifying blocked threads", thread.getName()); 270 } 271 released = true; 272 notifyAll(); 273 } 274 } 275}