5454#include " llvm/CAS/MappedFileRegionBumpPtr.h"
5555#include " OnDiskCommon.h"
5656#include " llvm/CAS/OnDiskCASLogger.h"
57+ #include " llvm/Support/Compiler.h"
58+
59+ #if LLVM_ON_UNIX
60+ #include < sys/stat.h>
61+ #if __has_include(<sys/param.h>)
62+ #include < sys/param.h>
63+ #endif
64+ #ifdef DEV_BSIZE
65+ #define MAPPED_FILE_BSIZE DEV_BSIZE
66+ #elif __linux__
67+ #define MAPPED_FILE_BSIZE 512
68+ #endif
69+ #endif
5770
5871using namespace llvm ;
5972using namespace llvm ::cas;
@@ -85,6 +98,13 @@ struct FileLockRAII {
8598 return Error::success ();
8699 }
87100};
101+
102+ struct FileSizeInfo {
103+ uint64_t Size;
104+ uint64_t AllocatedSize;
105+
106+ static ErrorOr<FileSizeInfo> get (sys::fs::file_t File);
107+ };
88108} // end anonymous namespace
89109
90110Expected<MappedFileRegionBumpPtr> MappedFileRegionBumpPtr::create (
@@ -123,39 +143,41 @@ Expected<MappedFileRegionBumpPtr> MappedFileRegionBumpPtr::create(
123143 return std::move (E);
124144
125145 sys::fs::file_t File = sys::fs::convertFDToNativeFile (FD);
126- sys::fs::file_status Status ;
127- if (std::error_code EC = sys::fs::status (File, Status) )
128- return createFileError (Result.Path , EC );
146+ auto FileSize = FileSizeInfo::get (File) ;
147+ if (!FileSize )
148+ return createFileError (Result.Path , FileSize. getError () );
129149
130- if (Status. getSize () < Capacity) {
150+ if (FileSize-> Size < Capacity) {
131151 // Lock the file exclusively so only one process will do the initialization.
132152 if (Error E = InitLock.unlock ())
133153 return std::move (E);
134154 if (Error E = InitLock.lock (FileLockRAII::Exclusive))
135155 return std::move (E);
136156 // Retrieve the current size now that we have exclusive access.
137- if (std::error_code EC = sys::fs::status (File, Status))
138- return createFileError (Result.Path , EC);
157+ FileSize = FileSizeInfo::get (File);
158+ if (!FileSize)
159+ return createFileError (Result.Path , FileSize.getError ());
139160 }
140161
141162 // At this point either the file is still under-sized, or we have the size for
142163 // the completely initialized file.
143164
144- if (Status. getSize () < Capacity) {
165+ if (FileSize-> Size < Capacity) {
145166 // We are initializing the file; it may be empty, or may have been shrunk
146167 // during a previous close.
147168 // FIXME: Detect a case where someone opened it with a smaller capacity.
148169 // FIXME: On Windows we should use FSCTL_SET_SPARSE and FSCTL_SET_ZERO_DATA
149170 // to make this a sparse region, if supported.
171+ assert (InitLock.Locked == FileLockRAII::Exclusive);
150172 if (std::error_code EC = sys::fs::resize_file (FD, Capacity))
151173 return createFileError (Result.Path , EC);
152174
153175 if (Result.Logger )
154176 Result.Logger ->log_MappedFileRegionBumpPtr_resizeFile (
155- Result.Path , Status. getSize () , Capacity);
177+ Result.Path , FileSize-> Size , Capacity);
156178 } else {
157179 // Someone else initialized it.
158- Capacity = Status. getSize () ;
180+ Capacity = FileSize-> Size ;
159181 }
160182
161183 // Create the mapped region.
@@ -168,14 +190,25 @@ Expected<MappedFileRegionBumpPtr> MappedFileRegionBumpPtr::create(
168190 Result.Region = std::move (Map);
169191 }
170192
171- if (Status.getSize () == 0 ) {
193+ if (FileSize->Size == 0 ) {
194+ assert (InitLock.Locked == FileLockRAII::Exclusive);
172195 // We are creating a new file; run the constructor.
173196 if (Error E = NewFileConstructor (Result))
174197 return std::move (E);
175198 } else {
176199 Result.initializeBumpPtr (BumpPtrOffset);
177200 }
178201
202+ if (FileSize->Size < Capacity && FileSize->AllocatedSize < Capacity) {
203+ // We are initializing the file; sync the allocated size in case it
204+ // changed when truncating or during construction.
205+ FileSize = FileSizeInfo::get (File);
206+ if (!FileSize)
207+ return createFileError (Result.Path , FileSize.getError ());
208+ assert (InitLock.Locked == FileLockRAII::Exclusive);
209+ Result.H ->AllocatedSize .exchange (FileSize->AllocatedSize );
210+ }
211+
179212 return Result;
180213}
181214
@@ -189,7 +222,7 @@ void MappedFileRegionBumpPtr::destroyImpl() {
189222
190223 // Attempt to truncate the file if we can get exclusive access. Ignore any
191224 // errors.
192- if (BumpPtr ) {
225+ if (H ) {
193226 assert (SharedLockFD && " Must have shared lock file open" );
194227 if (tryLockFileThreadSafe (*SharedLockFD) == std::error_code ()) {
195228 size_t Size = size ();
@@ -223,15 +256,15 @@ void MappedFileRegionBumpPtr::destroyImpl() {
223256
224257void MappedFileRegionBumpPtr::initializeBumpPtr (int64_t BumpPtrOffset) {
225258 assert (capacity () < (uint64_t )INT64_MAX && " capacity must fit in int64_t" );
226- int64_t BumpPtrEndOffset = BumpPtrOffset + sizeof (decltype (*BumpPtr ));
259+ int64_t BumpPtrEndOffset = BumpPtrOffset + sizeof (decltype (*H ));
227260 assert (BumpPtrEndOffset <= (int64_t )capacity () &&
228261 " Expected end offset to be pre-allocated" );
229- assert (isAligned (Align::Of<decltype (*BumpPtr )>(), BumpPtrOffset) &&
262+ assert (isAligned (Align::Of<decltype (*H )>(), BumpPtrOffset) &&
230263 " Expected end offset to be aligned" );
231- BumpPtr = reinterpret_cast <decltype (BumpPtr )>(data () + BumpPtrOffset);
264+ H = reinterpret_cast <decltype (H )>(data () + BumpPtrOffset);
232265
233266 int64_t ExistingValue = 0 ;
234- if (!BumpPtr-> compare_exchange_strong (ExistingValue, BumpPtrEndOffset))
267+ if (!H-> BumpPtr . compare_exchange_strong (ExistingValue, BumpPtrEndOffset))
235268 assert (ExistingValue >= BumpPtrEndOffset &&
236269 " Expected 0, or past the end of the BumpPtr itself" );
237270
@@ -247,7 +280,7 @@ static Error createAllocatorOutOfSpaceError() {
247280
248281Expected<int64_t > MappedFileRegionBumpPtr::allocateOffset (uint64_t AllocSize) {
249282 AllocSize = alignTo (AllocSize, getAlign ());
250- int64_t OldEnd = BumpPtr-> fetch_add (AllocSize);
283+ int64_t OldEnd = H-> BumpPtr . fetch_add (AllocSize);
251284 int64_t NewEnd = OldEnd + AllocSize;
252285 if (LLVM_UNLIKELY (NewEnd > (int64_t )capacity ())) {
253286 // Return the allocation. If the start already passed the end, that means
@@ -257,7 +290,7 @@ Expected<int64_t> MappedFileRegionBumpPtr::allocateOffset(uint64_t AllocSize) {
257290 // All other allocation afterwards must have failed and current allocation
258291 // is in charge of return the allocation back to a valid value.
259292 if (OldEnd <= (int64_t )capacity ())
260- (void )BumpPtr-> exchange (OldEnd);
293+ (void )H-> BumpPtr . exchange (OldEnd);
261294
262295 if (Logger)
263296 Logger->log_MappedFileRegionBumpPtr_oom (Path, capacity (), OldEnd,
@@ -266,8 +299,43 @@ Expected<int64_t> MappedFileRegionBumpPtr::allocateOffset(uint64_t AllocSize) {
266299 return createAllocatorOutOfSpaceError ();
267300 }
268301
302+ int64_t DiskSize = H->AllocatedSize ;
303+ if (LLVM_UNLIKELY (NewEnd > DiskSize)) {
304+ int64_t NewSize;
305+ // The minimum increment is a page, but allocate more to amortize the cost.
306+ constexpr int64_t Increment = 1 * 1024 * 1024 ; // 1 MB
307+ if (Error E = preallocateFileTail (*FD, DiskSize, DiskSize + Increment).moveInto (NewSize))
308+ return std::move (E);
309+ assert (NewSize >= DiskSize + Increment);
310+ // FIXME: on Darwin this can under-count the size if there is a race to
311+ // preallocate disk, because the semantics of F_PREALLOCATE are to add bytes
312+ // to the end of the file, not to allocate up to a fixed size.
313+ // Any discrepancy will be resolved the next time the file is truncated and
314+ // then reopend.
315+ while (DiskSize < NewSize)
316+ H->AllocatedSize .compare_exchange_strong (DiskSize, NewSize);
317+ }
318+
269319 if (Logger)
270320 Logger->log_MappedFileRegionBumpPtr_allocate (data (), OldEnd, AllocSize);
271321
272322 return OldEnd;
273323}
324+
325+ ErrorOr<FileSizeInfo> FileSizeInfo::get (sys::fs::file_t File) {
326+ #if LLVM_ON_UNIX && defined(MAPPED_FILE_BSIZE)
327+ struct stat Status;
328+ int StatRet = ::fstat (File, &Status);
329+ if (StatRet)
330+ return errnoAsErrorCode ();
331+ uint64_t AllocatedSize = uint64_t (Status.st_blksize ) * MAPPED_FILE_BSIZE;
332+ return FileSizeInfo{uint64_t (Status.st_size ), AllocatedSize};
333+ #else
334+ // Fallback: assume the file is fully allocated. Note: this may result in
335+ // data loss on out-of-space.
336+ sys::fs::file_status Status;
337+ if (std::error_code EC = sys::fs::status (File, Status))
338+ return EC;
339+ return FileSizeInfo{Status.getSize (), Status.getSize ()};
340+ #endif
341+ }
0 commit comments