001package org.apache.archiva.common.filelock; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.commons.lang3.time.StopWatch; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025import org.springframework.stereotype.Service; 026 027import java.io.FileNotFoundException; 028import java.io.IOException; 029import java.nio.channels.ClosedChannelException; 030import java.nio.file.Files; 031import java.nio.file.NoSuchFileException; 032import java.nio.file.Path; 033import java.util.concurrent.ConcurrentHashMap; 034import java.util.concurrent.ConcurrentMap; 035 036/** 037 * @author Olivier Lamy 038 * @since 2.0.0 039 */ 040@Service("fileLockManager#default") 041public class DefaultFileLockManager 042 implements FileLockManager 043{ 044 // TODO currently we create lock for read and write!! 045 // the idea could be to store lock here with various clients read/write 046 // only read could be a more simple lock and acquire a write lock means waiting the end of all reading threads 047 private static final ConcurrentMap<Path, Lock> lockFiles = new ConcurrentHashMap<Path, Lock>( 64 ); 048 049 private boolean skipLocking = true; 050 051 private Logger log = LoggerFactory.getLogger( getClass() ); 052 053 private int timeout = 0; 054 055 056 @Override 057 public Lock readFileLock( Path file ) 058 throws FileLockException, FileLockTimeoutException 059 { 060 if ( skipLocking ) 061 { 062 return new Lock( file ); 063 064 } 065 StopWatch stopWatch = new StopWatch(); 066 boolean acquired = false; 067 try { 068 mkdirs(file.getParent()); 069 } catch (IOException e) { 070 throw new FileLockException("Could not create directories "+file.getParent(), e); 071 } 072 073 Lock lock = null; 074 075 stopWatch.start(); 076 077 while ( !acquired ) 078 { 079 // Make sure that not a bad lock is returned, if a exception was thrown. 080 lock = null; 081 082 if ( timeout > 0 ) 083 { 084 long delta = stopWatch.getTime(); 085 log.debug( "delta {}, timeout {}", delta, timeout ); 086 if ( delta > timeout ) 087 { 088 log.warn( "Cannot acquire read lock within {} millis. Will skip the file: {}", timeout, file ); 089 // we could not get the lock within the timeout period, so throw FileLockTimeoutException 090 throw new FileLockTimeoutException(); 091 } 092 } 093 094 Lock current = lockFiles.get( file ); 095 096 if ( current != null ) 097 { 098 log.trace( "read lock file exist continue wait" ); 099 continue; 100 } 101 102 try 103 { 104 lock = new Lock( file, false ); 105 createNewFileQuietly( file ); 106 lock.openLock( false, timeout > 0 ); 107 // We are not returning an existing lock. If the lock is not 108 // exclusive, another thread may release the lock and the client 109 // knows nothing about it. 110 // The only atomic operation is the putIfAbsent operation, so if 111 // this returns null everything is OK, otherwise we should start at 112 // the beginning. 113 current = lockFiles.putIfAbsent( file, lock ); 114 if ( current == null ) 115 { 116 // Success 117 acquired = true; 118 } else { 119 // We try again 120 lock.close(); 121 lock=null; 122 } 123 } 124 catch ( FileNotFoundException | NoSuchFileException e ) 125 { 126 127 log.debug( "read Lock skip: {} try to create file", e.getMessage() ); 128 createNewFileQuietly( file ); 129 } 130 catch ( IOException e ) 131 { 132 throw new FileLockException( e.getMessage(), e ); 133 } 134 catch ( IllegalStateException e ) 135 { 136 log.trace( "openLock {}:{}", e.getClass(), e.getMessage() ); 137 } 138 } 139 140 return lock; 141 142 } 143 144 145 @Override 146 public Lock writeFileLock( Path file ) 147 throws FileLockException, FileLockTimeoutException 148 { 149 if ( skipLocking ) 150 { 151 return new Lock( file ); 152 } 153 154 try { 155 mkdirs( file.getParent() ); 156 } catch (IOException e) { 157 throw new FileLockException("Could not create directory "+file.getParent(), e); 158 } 159 160 StopWatch stopWatch = new StopWatch(); 161 boolean acquired = false; 162 163 Lock lock = null; 164 165 stopWatch.start(); 166 167 while ( !acquired ) 168 { 169 // Make sure that not a bad lock is returned, if a exception was thrown. 170 lock = null; 171 if ( timeout > 0 ) 172 { 173 long delta = stopWatch.getTime(); 174 log.debug( "delta {}, timeout {}", delta, timeout ); 175 if ( delta > timeout ) 176 { 177 log.warn( "Cannot acquire read lock within {} millis. Will skip the file: {}", timeout, file ); 178 // we could not get the lock within the timeout period, so throw FileLockTimeoutException 179 throw new FileLockTimeoutException(); 180 } 181 } 182 183 Lock current = lockFiles.get( file ); 184 185 try 186 { 187 188 if ( current != null ) 189 { 190 log.trace( "write lock file exist continue wait" ); 191 192 continue; 193 } 194 lock = new Lock( file, true ); 195 createNewFileQuietly( file ); 196 lock.openLock( true, timeout > 0 ); 197 // We are not returning an existing lock. If the lock is not 198 // exclusive, another thread may release the lock and the client 199 // knows nothing about it. 200 // The only atomic operation is the putIfAbsent operation, so if 201 // this returns null everything is OK, otherwise we should start at 202 // the beginning. 203 current = lockFiles.putIfAbsent( file, lock ); 204 if ( current == null ) 205 { 206 // Success 207 acquired = true; 208 } else { 209 // We try again 210 lock.close(); 211 lock=null; 212 } 213 } 214 catch ( FileNotFoundException | NoSuchFileException e ) 215 { 216 217 log.debug( "write Lock skip: {} try to create file", e.getMessage() ); 218 createNewFileQuietly( file ); 219 } 220 catch ( IOException e ) 221 { 222 throw new FileLockException( e.getMessage(), e ); 223 } 224 catch ( IllegalStateException e ) 225 { 226 log.trace( "openLock {}:{}", e.getClass(), e.getMessage() ); 227 } 228 } 229 230 return lock; 231 232 233 } 234 235 private void createNewFileQuietly( Path file ) 236 { 237 try 238 { 239 Files.createFile(file); 240 } 241 catch ( IOException e ) 242 { 243 // skip that 244 } 245 } 246 247 @Override 248 public void release( Lock lock ) 249 throws FileLockException 250 { 251 if ( lock == null ) 252 { 253 log.debug( "skip releasing null" ); 254 return; 255 } 256 if ( skipLocking ) 257 { 258 return; 259 } 260 try 261 { 262 lockFiles.remove( lock.getFile() ); 263 lock.close(); 264 } 265 catch ( ClosedChannelException e ) 266 { 267 // skip this one 268 log.debug( "ignore ClosedChannelException: {}", e.getMessage() ); 269 } 270 catch ( IOException e ) 271 { 272 throw new FileLockException( e.getMessage(), e ); 273 } 274 } 275 276 @Override 277 public void clearLockFiles() 278 { 279 lockFiles.clear(); 280 } 281 282 private Path mkdirs( Path directory ) throws IOException { 283 return Files.createDirectories(directory); 284 } 285 286 @Override 287 public int getTimeout() 288 { 289 return timeout; 290 } 291 292 @Override 293 public void setTimeout( int timeout ) 294 { 295 this.timeout = timeout; 296 } 297 298 @Override 299 public boolean isSkipLocking() 300 { 301 return skipLocking; 302 } 303 304 @Override 305 public void setSkipLocking( boolean skipLocking ) 306 { 307 this.skipLocking = skipLocking; 308 } 309}