PageRenderTime 59ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/cpp/core/file_lock/LinkBasedFileLock.cpp

http://github.com/rstudio/rstudio
C++ | 487 lines | 337 code | 84 blank | 66 comment | 33 complexity | 23f926b573a384bcef6c2306d34ce52a MD5 | raw file
Possible License(s): AGPL-3.0, MIT, MPL-2.0-no-copyleft-exception, BSD-3-Clause, Apache-2.0
  1. /*
  2. * LinkBasedFileLock.cpp
  3. *
  4. * Copyright (C) 2021 by RStudio, PBC
  5. *
  6. * Unless you have received this program directly from RStudio pursuant
  7. * to the terms of a commercial license agreement with RStudio, then
  8. * this program is licensed to you under the terms of version 3 of the
  9. * GNU Affero General Public License. This program is distributed WITHOUT
  10. * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
  11. * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
  12. * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
  13. *
  14. */
  15. #include <core/FileLock.hpp>
  16. #include <errno.h>
  17. #include <sys/types.h>
  18. #include <sys/stat.h>
  19. #include <fcntl.h>
  20. #ifdef _MSC_VER
  21. # include <io.h>
  22. #else
  23. # include <unistd.h>
  24. #endif
  25. #include <set>
  26. #include <vector>
  27. #include <shared_core/SafeConvert.hpp>
  28. #include <core/Algorithm.hpp>
  29. #include <core/Thread.hpp>
  30. #include <shared_core/Error.hpp>
  31. #include <core/Log.hpp>
  32. #include <shared_core/FilePath.hpp>
  33. #include <core/FileSerializer.hpp>
  34. #include <core/system/Process.hpp>
  35. #include <core/system/System.hpp>
  36. #include <boost/system/error_code.hpp>
  37. #define LOG(__X__) \
  38. do \
  39. { \
  40. std::stringstream ss; \
  41. ss << "(PID " << ::getpid() << "): " << __X__ << std::endl; \
  42. ::rstudio::core::FileLock::log(ss.str()); \
  43. } while (0)
  44. namespace rstudio {
  45. namespace core {
  46. namespace {
  47. const char * const kFileLockPrefix = ".rstudio-lock-41c29";
  48. std::string pidString()
  49. {
  50. PidType pid = system::currentProcessId();
  51. return safe_convert::numberToString((long) pid);
  52. }
  53. std::string hostName()
  54. {
  55. char buffer[256];
  56. int status = ::gethostname(buffer, 255);
  57. if (status)
  58. LOG_ERROR(systemError(errno, ERROR_LOCATION));
  59. return std::string(buffer);
  60. }
  61. std::string threadId()
  62. {
  63. std::stringstream ss;
  64. ss << boost::this_thread::get_id();
  65. return ss.str();
  66. }
  67. std::string proxyLockFileName()
  68. {
  69. return std::string()
  70. + kFileLockPrefix
  71. + "-" + hostName()
  72. + "-" + pidString()
  73. + "-" + threadId();
  74. }
  75. bool isLockFileStale(const FilePath& lockFilePath)
  76. {
  77. return LinkBasedFileLock::isLockFileStale(lockFilePath);
  78. }
  79. bool isLockFileOrphaned(const FilePath& lockFilePath)
  80. {
  81. #ifndef _WIN32
  82. Error error;
  83. // attempt to read pid from lockfile
  84. std::string pid;
  85. error = core::readStringFromFile(lockFilePath, &pid);
  86. if (error)
  87. {
  88. LOG_ERROR(error);
  89. return false;
  90. }
  91. pid = string_utils::trimWhitespace(pid);
  92. #ifdef __linux__
  93. // on linux, we can check the proc filesystem for an associated
  94. // process -- if there is no such directory, then we assume that
  95. // this lockfile has been orphaned
  96. FilePath procPath("/proc/" + pid);
  97. if (!procPath.exists())
  98. return true;
  99. #endif
  100. // call 'ps' to attempt to see if a process associated
  101. // with this process id exists (and get information about it)
  102. using namespace core::system;
  103. std::string command = "ps -p " + pid;
  104. ProcessOptions options;
  105. ProcessResult result;
  106. error = core::system::runCommand(command, options, &result);
  107. if (error)
  108. {
  109. LOG_ERROR(error);
  110. return false;
  111. }
  112. // ps will return a non-zero exit status if no process with
  113. // the requested id is available -- if there is no process,
  114. // then this lockfile has been orphaned
  115. if (result.exitStatus != EXIT_SUCCESS)
  116. return true;
  117. #endif /* _WIN32 */
  118. // assume the process is not orphaned if all previous checks failed
  119. return false;
  120. }
  121. } // end anonymous namespace
  122. bool LinkBasedFileLock::isLockFileStale(const FilePath& lockFilePath)
  123. {
  124. // TODO: currently, we write the process ID of the owning process to the
  125. // lockfile, in order to detect whether the owning process has crashed
  126. // and the lockfile is orphaned. in load-balanced configurations, this is
  127. // unreliable as sessions across multiple machines may be attempting to
  128. // read / write lockfiles, so we disable this check here
  129. if (!s_isLoadBalanced)
  130. {
  131. if (isLockFileOrphaned(lockFilePath))
  132. return true;
  133. }
  134. double seconds = static_cast<double>(s_timeoutInterval.total_seconds());
  135. double diff = ::difftime(::time(nullptr), lockFilePath.getLastWriteTime());
  136. return diff >= seconds;
  137. }
  138. namespace {
  139. void cleanStaleLockfiles(const FilePath& dir)
  140. {
  141. std::vector<FilePath> children;
  142. Error error = dir.getChildren(children);
  143. if (error)
  144. LOG_ERROR(error);
  145. for (const FilePath& filePath : children )
  146. {
  147. if (boost::algorithm::starts_with(filePath.getFilename(), kFileLockPrefix) &&
  148. isLockFileStale(filePath))
  149. {
  150. Error error = filePath.removeIfExists();
  151. if (error)
  152. LOG_ERROR(error);
  153. }
  154. }
  155. }
  156. class LockRegistration : boost::noncopyable
  157. {
  158. public:
  159. void registerLock(const FilePath& lockFilePath)
  160. {
  161. LOCK_MUTEX(mutex_)
  162. {
  163. registration_.insert(lockFilePath);
  164. }
  165. END_LOCK_MUTEX
  166. }
  167. void deregisterLock(const FilePath& lockFilePath)
  168. {
  169. LOCK_MUTEX(mutex_)
  170. {
  171. registration_.erase(lockFilePath);
  172. }
  173. END_LOCK_MUTEX
  174. }
  175. void refreshLocks()
  176. {
  177. LOCK_MUTEX(mutex_)
  178. {
  179. for (const FilePath& lockFilePath : registration_)
  180. {
  181. LOG("Bumping write time: " << lockFilePath.getAbsolutePath());
  182. lockFilePath.setLastWriteTime();
  183. }
  184. }
  185. END_LOCK_MUTEX
  186. }
  187. void clearLocks()
  188. {
  189. LOCK_MUTEX(mutex_)
  190. {
  191. for (const FilePath& lockFilePath : registration_)
  192. {
  193. Error error = lockFilePath.removeIfExists();
  194. if (error)
  195. LOG_ERROR(error);
  196. LOG("Clearing lock: " << lockFilePath.getAbsolutePath());
  197. }
  198. registration_.clear();
  199. }
  200. END_LOCK_MUTEX
  201. }
  202. private:
  203. boost::mutex mutex_;
  204. std::set<FilePath> registration_;
  205. };
  206. LockRegistration& lockRegistration()
  207. {
  208. static LockRegistration instance;
  209. return instance;
  210. }
  211. Error writeLockFile(const FilePath& lockFilePath)
  212. {
  213. #ifndef _WIN32
  214. // generate proxy lockfile
  215. FilePath proxyPath = lockFilePath.getParent().completePath(proxyLockFileName());
  216. // since the proxy lockfile should be unique, it should _never_ be possible
  217. // for a collision to be found. if that does happen, it must be a leftover
  218. // from a previous process that crashed in this stage
  219. Error error = proxyPath.removeIfExists();
  220. if (error)
  221. LOG_ERROR(error);
  222. // ensure the proxy file is created, and remove it when we're done
  223. std::string pid = pidString();
  224. RemoveOnExitScope scope(proxyPath, ERROR_LOCATION);
  225. error = core::writeStringToFile(proxyPath, pid);
  226. if (error)
  227. {
  228. // log the error since it isn't expected and could get swallowed
  229. // upstream by a caller ignore lock_not_available errors
  230. LOG_ERROR(error);
  231. return error;
  232. }
  233. // attempt to link to the desired location -- ignore return value
  234. // and just stat our original link after, as that's a more reliable
  235. // indicator of success on old NFS systems
  236. int status = ::link(
  237. proxyPath.getAbsolutePathNative().c_str(),
  238. lockFilePath.getAbsolutePathNative().c_str());
  239. // detect link failure
  240. if (status == -1)
  241. {
  242. // verbose logging
  243. int errorNumber = errno;
  244. LOG("ERROR: ::link() failed (errno " << errorNumber << ")" << std::endl <<
  245. "Attempted to link:" << std::endl << " - " <<
  246. "'" << proxyPath.getAbsolutePathNative() << "'" <<
  247. " => " <<
  248. "'" << lockFilePath.getAbsolutePathNative() << "'");
  249. // if this failed, we should still make a best-effort attempt to acquire
  250. // a lock by creating a file using O_CREAT | O_EXCL. note that we prefer
  251. // ::link() since older NFSes provide more guarantees as to its atomicity,
  252. // but not all NFS support ::link()
  253. int fd = ::open(
  254. lockFilePath.getAbsolutePathNative().c_str(),
  255. O_WRONLY | O_CREAT | O_EXCL,
  256. 0755);
  257. if (fd == -1)
  258. {
  259. // verbose logging
  260. int errorNumber = errno;
  261. LOG("ERROR: ::open() failed (errno " << errorNumber << ")" <<
  262. std::endl << "Attempted to open:" << std::endl << " - " <<
  263. "'" << lockFilePath.getAbsolutePathNative() << "'");
  264. Error error = systemError(errorNumber, ERROR_LOCATION);
  265. error.addProperty("lock-file", lockFilePath);
  266. return error;
  267. }
  268. // acquired file descriptor -- now try writing our pid to the file
  269. // (save error number in case it fails and we need to report)
  270. int status = ::write(fd, pid.c_str(), pid.size());
  271. errorNumber = errno;
  272. // close file descriptor
  273. ::close(fd);
  274. // report if an error occurred during write
  275. if (status)
  276. {
  277. Error error = systemError(errorNumber, ERROR_LOCATION);
  278. error.addProperty("lock-file", lockFilePath);
  279. return error;
  280. }
  281. return Success();
  282. }
  283. struct stat info;
  284. int errc = ::stat(proxyPath.getAbsolutePathNative().c_str(), &info);
  285. if (errc)
  286. {
  287. int errorNumber = errno;
  288. // verbose logging
  289. LOG("ERROR: ::stat() failed (errno " << errorNumber << ")" << std::endl <<
  290. "Attempted to stat:" << std::endl << " - " <<
  291. "'" << proxyPath.getAbsolutePathNative() << "'");
  292. // log the error since it isn't expected and could get swallowed
  293. // upstream by a caller ignoring lock_not_available errors
  294. Error error = systemError(errorNumber, ERROR_LOCATION);
  295. LOG_ERROR(error);
  296. return error;
  297. }
  298. // assume that a failure here is the result of someone else
  299. // acquiring the lock before we could
  300. if (info.st_nlink != 2)
  301. {
  302. LOG("WARNING: Failed to acquire lock (info.st_nlink == " << info.st_nlink << ")");
  303. return fileExistsError(ERROR_LOCATION);
  304. }
  305. return Success();
  306. #else
  307. return systemError(boost::system::errc::function_not_supported, ERROR_LOCATION);
  308. #endif
  309. }
  310. } // end anonymous namespace
  311. struct LinkBasedFileLock::Impl
  312. {
  313. FilePath lockFilePath;
  314. };
  315. LinkBasedFileLock::LinkBasedFileLock()
  316. : pImpl_(new Impl())
  317. {
  318. }
  319. LinkBasedFileLock::~LinkBasedFileLock()
  320. {
  321. }
  322. FilePath LinkBasedFileLock::lockFilePath() const
  323. {
  324. return pImpl_->lockFilePath;
  325. }
  326. bool LinkBasedFileLock::isLocked(const FilePath& lockFilePath) const
  327. {
  328. if (!lockFilePath.exists())
  329. return false;
  330. return !isLockFileStale(lockFilePath);
  331. }
  332. Error LinkBasedFileLock::acquire(const FilePath& lockFilePath)
  333. {
  334. using namespace boost::system;
  335. // if the lock file exists...
  336. if (lockFilePath.exists())
  337. {
  338. // ... and it's stale, it's a leftover lock from a previously
  339. // (crashed?) process. remove it and acquire our own lock
  340. if (isLockFileStale(lockFilePath))
  341. {
  342. // note that multiple processes may attempt to remove this
  343. // file at the same time, so errors shouldn't be fatal
  344. LOG("Removing stale lockfile: " << lockFilePath.getAbsolutePath());
  345. Error error = lockFilePath.remove();
  346. if (error)
  347. LOG_ERROR(error);
  348. }
  349. // ... it's not stale -- someone else has the lock, cannot proceed
  350. else
  351. {
  352. LOG("No lock available: " << lockFilePath.getAbsolutePath());
  353. Error error = systemError(errc::no_lock_available, ERROR_LOCATION);
  354. error.addProperty("lock-file", lockFilePath);
  355. return error;
  356. }
  357. }
  358. // ensure the parent directory exists
  359. Error error = lockFilePath.getParent().ensureDirectory();
  360. if (error)
  361. return error;
  362. // write the lock file -- this step _must_ be atomic and so only one
  363. // competing process should be able to succeed here
  364. Error writeError = writeLockFile(lockFilePath);
  365. if (writeError)
  366. {
  367. LOG("Failed to acquire lock: " << lockFilePath.getAbsolutePath());
  368. Error error = systemError(
  369. errc::no_lock_available,
  370. writeError,
  371. ERROR_LOCATION);
  372. error.addProperty("lock-file", lockFilePath);
  373. return error;
  374. }
  375. // clean any other stale lockfiles in that directory
  376. cleanStaleLockfiles(lockFilePath.getParent());
  377. // register our lock (for refresh)
  378. pImpl_->lockFilePath = lockFilePath;
  379. lockRegistration().registerLock(lockFilePath);
  380. LOG("Acquired lock: " << lockFilePath.getAbsolutePath());
  381. return Success();
  382. }
  383. Error LinkBasedFileLock::release()
  384. {
  385. const FilePath& lockFilePath = pImpl_->lockFilePath;
  386. LOG("Released lock: " << lockFilePath.getAbsolutePath());
  387. Error error = lockFilePath.remove();
  388. if (error)
  389. LOG_ERROR(error);
  390. lockRegistration().deregisterLock(lockFilePath);
  391. pImpl_->lockFilePath = FilePath();
  392. return error;
  393. }
  394. void LinkBasedFileLock::refresh()
  395. {
  396. lockRegistration().refreshLocks();
  397. }
  398. void LinkBasedFileLock::cleanUp()
  399. {
  400. lockRegistration().clearLocks();
  401. }
  402. } // namespace core
  403. } // namespace rstudio