summaryrefslogtreecommitdiffstats
path: root/netwerk/cache2/CacheFileIOManager.h
blob: 20a4b8f2c18043d33992a0e434bf1663d2366c94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef CacheFileIOManager__h__
#define CacheFileIOManager__h__

#include "CacheIOThread.h"
#include "CacheStorageService.h"
#include "CacheHashUtils.h"
#include "nsIEventTarget.h"
#include "nsINamed.h"
#include "nsITimer.h"
#include "nsCOMPtr.h"
#include "mozilla/Atomics.h"
#include "mozilla/SHA1.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TimeStamp.h"
#include "nsTArray.h"
#include "nsString.h"
#include "nsTHashtable.h"
#include "prio.h"

// #define DEBUG_HANDLES 1

class nsIFile;
class nsITimer;
class nsIDirectoryEnumerator;
class nsILoadContextInfo;

namespace mozilla {
namespace net {

class CacheFile;
class CacheFileIOListener;

#ifdef DEBUG_HANDLES
class CacheFileHandlesEntry;
#endif

#define ENTRIES_DIR "entries"
#define DOOMED_DIR "doomed"
#define TRASH_DIR "trash"

class CacheFileHandle final : public nsISupports {
 public:
  enum class PinningStatus : uint32_t { UNKNOWN, NON_PINNED, PINNED };

  NS_DECL_THREADSAFE_ISUPPORTS
  bool DispatchRelease();

  CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority,
                  PinningStatus aPinning);
  CacheFileHandle(const nsACString& aKey, bool aPriority,
                  PinningStatus aPinning);
  void Log();
  bool IsDoomed() const { return mIsDoomed; }
  const SHA1Sum::Hash* Hash() const { return mHash; }
  int64_t FileSize() const { return mFileSize; }
  uint32_t FileSizeInK() const;
  bool IsPriority() const { return mPriority; }
  bool FileExists() const { return mFileExists; }
  bool IsClosed() const { return mClosed; }
  bool IsSpecialFile() const { return mSpecialFile; }
  nsCString& Key() { return mKey; }

  // Returns false when this handle has been doomed based on the pinning state
  // update.
  bool SetPinned(bool aPinned);
  void SetInvalid() { mInvalid = true; }

  // Memory reporting
  size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
  size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;

 private:
  friend class CacheFileIOManager;
  friend class CacheFileHandles;
  friend class ReleaseNSPRHandleEvent;

  virtual ~CacheFileHandle();

  const SHA1Sum::Hash* mHash;
  mozilla::Atomic<bool, ReleaseAcquire> mIsDoomed;
  mozilla::Atomic<bool, ReleaseAcquire> mClosed;

  // mPriority and mSpecialFile are plain "bool", not "bool:1", so as to
  // avoid bitfield races with the byte containing mInvalid et al.  See
  // bug 1278502.
  bool const mPriority;
  bool const mSpecialFile;

  mozilla::Atomic<bool, Relaxed> mInvalid;

  // These bit flags are all accessed only on the IO thread
  bool mFileExists : 1;  // This means that the file should exists,
                         // but it can be still deleted by OS/user
                         // and then a subsequent OpenNSPRFileDesc()
                         // will fail.

  // Both initially false.  Can be raised to true only when this handle is to be
  // doomed during the period when the pinning status is unknown.  After the
  // pinning status determination we check these flags and possibly doom. These
  // flags are only accessed on the IO thread.
  bool mDoomWhenFoundPinned : 1;
  bool mDoomWhenFoundNonPinned : 1;
  // Set when after shutdown AND:
  // - when writing: writing data (not metadata) OR the physical file handle is
  //   not currently open
  // - when truncating: the physical file handle is not currently open
  // When set it prevents any further writes or truncates on such handles to
  // happen immediately after shutdown and gives a chance to write metadata of
  // already open files quickly as possible (only that renders them actually
  // usable by the cache.)
  bool mKilled : 1;
  // For existing files this is always pre-set to UNKNOWN.  The status is
  // udpated accordingly after the matadata has been parsed. For new files the
  // flag is set according to which storage kind is opening the cache entry and
  // remains so for the handle's lifetime. The status can only change from
  // UNKNOWN (if set so initially) to one of PINNED or NON_PINNED and it stays
  // unchanged afterwards. This status is only accessed on the IO thread.
  PinningStatus mPinning;

  nsCOMPtr<nsIFile> mFile;

  // file size is atomic because it is used on main thread by
  // nsHttpChannel::ReportNetVSCacheTelemetry()
  Atomic<int64_t, Relaxed> mFileSize;
  PRFileDesc* mFD;  // if null then the file doesn't exists on the disk
  nsCString mKey;
};

class CacheFileHandles {
 public:
  CacheFileHandles();
  ~CacheFileHandles();

  nsresult GetHandle(const SHA1Sum::Hash* aHash, CacheFileHandle** _retval);
  already_AddRefed<CacheFileHandle> NewHandle(const SHA1Sum::Hash*,
                                              bool aPriority,
                                              CacheFileHandle::PinningStatus);
  void RemoveHandle(CacheFileHandle* aHandle);
  void GetAllHandles(nsTArray<RefPtr<CacheFileHandle> >* _retval);
  void GetActiveHandles(nsTArray<RefPtr<CacheFileHandle> >* _retval);
  void ClearAll();
  uint32_t HandleCount();

#ifdef DEBUG_HANDLES
  void Log(CacheFileHandlesEntry* entry);
#endif

  // Memory reporting
  size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
  size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;

  class HandleHashKey : public PLDHashEntryHdr {
   public:
    using KeyType = const SHA1Sum::Hash&;
    using KeyTypePointer = const SHA1Sum::Hash*;

    explicit HandleHashKey(KeyTypePointer aKey) {
      MOZ_COUNT_CTOR(HandleHashKey);
      mHash = MakeUnique<uint8_t[]>(SHA1Sum::kHashSize);
      memcpy(mHash.get(), aKey, sizeof(SHA1Sum::Hash));
    }
    HandleHashKey(const HandleHashKey& aOther) {
      MOZ_ASSERT_UNREACHABLE("HandleHashKey copy constructor is forbidden!");
    }
    MOZ_COUNTED_DTOR(HandleHashKey)

    bool KeyEquals(KeyTypePointer aKey) const {
      return memcmp(mHash.get(), aKey, sizeof(SHA1Sum::Hash)) == 0;
    }
    static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
    static PLDHashNumber HashKey(KeyTypePointer aKey) {
      return (reinterpret_cast<const uint32_t*>(aKey))[0];
    }

    void AddHandle(CacheFileHandle* aHandle);
    void RemoveHandle(CacheFileHandle* aHandle);
    already_AddRefed<CacheFileHandle> GetNewestHandle();
    void GetHandles(nsTArray<RefPtr<CacheFileHandle> >& aResult);

    SHA1Sum::Hash* Hash() const {
      return reinterpret_cast<SHA1Sum::Hash*>(mHash.get());
    }
    bool IsEmpty() const { return mHandles.Length() == 0; }

    enum { ALLOW_MEMMOVE = true };

#ifdef DEBUG
    void AssertHandlesState();
#endif

    // Memory reporting
    size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
    size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;

   private:
    // We can't make this UniquePtr<SHA1Sum::Hash>, because you can't have
    // UniquePtrs with known bounds.  So we settle for this representation
    // and using appropriate casts when we need to access it as a
    // SHA1Sum::Hash.
    UniquePtr<uint8_t[]> mHash;
    // Use weak pointers since the hash table access is on a single thread
    // only and CacheFileHandle removes itself from this table in its dtor
    // that may only be called on the same thread as we work with the hashtable
    // since we dispatch its Release() to this thread.
    nsTArray<CacheFileHandle*> mHandles;
  };

 private:
  nsTHashtable<HandleHashKey> mTable;
};

////////////////////////////////////////////////////////////////////////////////

class OpenFileEvent;
class ReadEvent;
class WriteEvent;
class MetadataWriteScheduleEvent;
class CacheFileContextEvictor;

#define CACHEFILEIOLISTENER_IID                      \
  { /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */       \
    0xdcaf2ddc, 0x17cf, 0x4242, {                    \
      0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5 \
    }                                                \
  }

class CacheFileIOListener : public nsISupports {
 public:
  NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEIOLISTENER_IID)

  NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) = 0;
  NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
                           nsresult aResult) = 0;
  NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
                        nsresult aResult) = 0;
  NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) = 0;
  NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) = 0;
  NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) = 0;

  virtual bool IsKilled() { return false; }
};

NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID)

class CacheFileIOManager final : public nsITimerCallback, public nsINamed {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSITIMERCALLBACK
  NS_DECL_NSINAMED

  enum {
    OPEN = 0U,
    CREATE = 1U,
    CREATE_NEW = 2U,
    PRIORITY = 4U,
    SPECIAL_FILE = 8U,
    PINNED = 16U
  };

  CacheFileIOManager();

  static nsresult Init();
  static nsresult Shutdown();
  static nsresult OnProfile();
  static nsresult OnDelayedStartupFinished();
  static already_AddRefed<nsIEventTarget> IOTarget();
  static already_AddRefed<CacheIOThread> IOThread();
  static bool IsOnIOThread();
  static bool IsOnIOThreadOrCeased();
  static bool IsShutdown();

  // Make aFile's WriteMetadataIfNeeded be called automatically after
  // a short interval.
  static nsresult ScheduleMetadataWrite(CacheFile* aFile);
  // Remove aFile from the scheduling registry array.
  // WriteMetadataIfNeeded will not be automatically called.
  static nsresult UnscheduleMetadataWrite(CacheFile* aFile);
  // Shuts the scheduling off and flushes all pending metadata writes.
  static nsresult ShutdownMetadataWriteScheduling();

  static nsresult OpenFile(const nsACString& aKey, uint32_t aFlags,
                           CacheFileIOListener* aCallback);
  static nsresult Read(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
                       int32_t aCount, CacheFileIOListener* aCallback);
  // This function must be called with a callback. The caller is responsible for
  // releasing |aBuf|.
  static nsresult Write(CacheFileHandle* aHandle, int64_t aOffset,
                        const char* aBuf, int32_t aCount, bool aValidate,
                        bool aTruncate, CacheFileIOListener* aCallback);
  // Similar to the above, but without the callback. Note that |aBuf| will be
  // released by CacheFileIOManager.
  static nsresult WriteWithoutCallback(CacheFileHandle* aHandle,
                                       int64_t aOffset, char* aBuf,
                                       int32_t aCount, bool aValidate,
                                       bool aTruncate);
  // PinningDoomRestriction:
  // NO_RESTRICTION
  //    no restriction is checked, the file is simply always doomed
  // DOOM_WHEN_(NON)_PINNED, we branch based on the pinning status of the
  // handle:
  //   UNKNOWN: the handle is marked to be doomed when later found (non)pinned
  //   PINNED/NON_PINNED: doom only when the restriction matches the pin status
  //      and the handle has not yet been required to doom during the UNKNOWN
  //      period
  enum PinningDoomRestriction {
    NO_RESTRICTION,
    DOOM_WHEN_NON_PINNED,
    DOOM_WHEN_PINNED
  };
  static nsresult DoomFile(CacheFileHandle* aHandle,
                           CacheFileIOListener* aCallback);
  static nsresult DoomFileByKey(const nsACString& aKey,
                                CacheFileIOListener* aCallback);
  static nsresult ReleaseNSPRHandle(CacheFileHandle* aHandle);
  static nsresult TruncateSeekSetEOF(CacheFileHandle* aHandle,
                                     int64_t aTruncatePos, int64_t aEOFPos,
                                     CacheFileIOListener* aCallback);
  static nsresult RenameFile(CacheFileHandle* aHandle,
                             const nsACString& aNewName,
                             CacheFileIOListener* aCallback);
  static nsresult EvictIfOverLimit();
  static nsresult EvictAll();
  static nsresult EvictByContext(nsILoadContextInfo* aLoadContextInfo,
                                 bool aPinned, const nsAString& aOrigin,
                                 const nsAString& aBaseDomain = u""_ns);

  static nsresult InitIndexEntry(CacheFileHandle* aHandle,
                                 OriginAttrsHash aOriginAttrsHash,
                                 bool aAnonymous, bool aPinning);
  static nsresult UpdateIndexEntry(CacheFileHandle* aHandle,
                                   const uint32_t* aFrecency,
                                   const bool* aHasAltData,
                                   const uint16_t* aOnStartTime,
                                   const uint16_t* aOnStopTime,
                                   const uint8_t* aContentType);

  static nsresult UpdateIndexEntry();

  enum EEnumerateMode { ENTRIES, DOOMED };

  static void GetCacheDirectory(nsIFile** result);
#if defined(MOZ_WIDGET_ANDROID)
  static void GetProfilelessCacheDirectory(nsIFile** result);
#endif

  // Calls synchronously OnEntryInfo for an entry with the given hash.
  // Tries to find an existing entry in the service hashtables first, if not
  // found, loads synchronously from disk file.
  // Callable on the IO thread only.
  static nsresult GetEntryInfo(
      const SHA1Sum::Hash* aHash,
      CacheStorageService::EntryInfoCallback* aCallback);

  // Memory reporting
  static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
  static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);

 private:
  friend class CacheFileHandle;
  friend class CacheFileChunk;
  friend class CacheFile;
  friend class ShutdownEvent;
  friend class OpenFileEvent;
  friend class CloseHandleEvent;
  friend class ReadEvent;
  friend class WriteEvent;
  friend class DoomFileEvent;
  friend class DoomFileByKeyEvent;
  friend class ReleaseNSPRHandleEvent;
  friend class TruncateSeekSetEOFEvent;
  friend class RenameFileEvent;
  friend class CacheIndex;
  friend class MetadataWriteScheduleEvent;
  friend class CacheFileContextEvictor;

  virtual ~CacheFileIOManager();

  nsresult InitInternal();
  void ShutdownInternal();

  nsresult OpenFileInternal(const SHA1Sum::Hash* aHash, const nsACString& aKey,
                            uint32_t aFlags, CacheFileHandle** _retval);
  nsresult OpenSpecialFileInternal(const nsACString& aKey, uint32_t aFlags,
                                   CacheFileHandle** _retval);
  void CloseHandleInternal(CacheFileHandle* aHandle);
  nsresult ReadInternal(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
                        int32_t aCount);
  nsresult WriteInternal(CacheFileHandle* aHandle, int64_t aOffset,
                         const char* aBuf, int32_t aCount, bool aValidate,
                         bool aTruncate);
  nsresult DoomFileInternal(
      CacheFileHandle* aHandle,
      PinningDoomRestriction aPinningDoomRestriction = NO_RESTRICTION);
  nsresult DoomFileByKeyInternal(const SHA1Sum::Hash* aHash);
  nsresult MaybeReleaseNSPRHandleInternal(CacheFileHandle* aHandle,
                                          bool aIgnoreShutdownLag = false);
  nsresult TruncateSeekSetEOFInternal(CacheFileHandle* aHandle,
                                      int64_t aTruncatePos, int64_t aEOFPos);
  nsresult RenameFileInternal(CacheFileHandle* aHandle,
                              const nsACString& aNewName);
  nsresult EvictIfOverLimitInternal();
  nsresult OverLimitEvictionInternal();
  nsresult EvictAllInternal();
  nsresult EvictByContextInternal(nsILoadContextInfo* aLoadContextInfo,
                                  bool aPinned, const nsAString& aOrigin,
                                  const nsAString& aBaseDomain = u""_ns);

  nsresult TrashDirectory(nsIFile* aFile);
  static void OnTrashTimer(nsITimer* aTimer, void* aClosure);
  nsresult StartRemovingTrash();
  nsresult RemoveTrashInternal();
  nsresult FindTrashDirToRemove();

  nsresult CreateFile(CacheFileHandle* aHandle);
  static void HashToStr(const SHA1Sum::Hash* aHash, nsACString& _retval);
  static nsresult StrToHash(const nsACString& aHash, SHA1Sum::Hash* _retval);
  nsresult GetFile(const SHA1Sum::Hash* aHash, nsIFile** _retval);
  nsresult GetSpecialFile(const nsACString& aKey, nsIFile** _retval);
  nsresult GetDoomedFile(nsIFile** _retval);
  nsresult IsEmptyDirectory(nsIFile* aFile, bool* _retval);
  nsresult CheckAndCreateDir(nsIFile* aFile, const char* aDir,
                             bool aEnsureEmptyDir);
  nsresult CreateCacheTree();
  nsresult OpenNSPRHandle(CacheFileHandle* aHandle, bool aCreate = false);
  void NSPRHandleUsed(CacheFileHandle* aHandle);

  // Removing all cache files during shutdown
  nsresult SyncRemoveDir(nsIFile* aFile, const char* aDir);
  void SyncRemoveAllCacheFiles();

  nsresult ScheduleMetadataWriteInternal(CacheFile* aFile);
  void UnscheduleMetadataWriteInternal(CacheFile* aFile);
  void ShutdownMetadataWriteSchedulingInternal();

  static nsresult CacheIndexStateChanged();
  void CacheIndexStateChangedInternal();

  // Dispatches a purgeHTTP background task to delete the cache directoy
  // indicated by aCacheDirName.
  // When this feature is enabled, a task will be dispatched at shutdown
  // or after browser startup (to cleanup potential left-over directories)
  nsresult DispatchPurgeTask(const nsCString& aCacheDirName,
                             const nsCString& aSecondsToWait,
                             const nsCString& aPurgeExtension);

  // Smart size calculation. UpdateSmartCacheSize() must be called on IO thread.
  // It is called in EvictIfOverLimitInternal() just before we decide whether to
  // start overlimit eviction or not and also in OverLimitEvictionInternal()
  // before we start an eviction loop.
  nsresult UpdateSmartCacheSize(int64_t aFreeSpace);

  // Memory reporting (private part)
  size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;

  static StaticRefPtr<CacheFileIOManager> gInstance;

  TimeStamp mStartTime;
  // Set true on the IO thread, CLOSE level as part of the internal shutdown
  // procedure.
  bool mShuttingDown{false};
  RefPtr<CacheIOThread> mIOThread;
  nsCOMPtr<nsIFile> mCacheDirectory;
#if defined(MOZ_WIDGET_ANDROID)
  // On Android we add the active profile directory name between the path
  // and the 'cache2' leaf name.  However, to delete any leftover data from
  // times before we were doing it, we still need to access the directory
  // w/o the profile name in the path.  Here it is stored.
  nsCOMPtr<nsIFile> mCacheProfilelessDirectory;
#endif
  bool mTreeCreated{false};
  bool mTreeCreationFailed{false};
  CacheFileHandles mHandles;
  nsTArray<CacheFileHandle*> mHandlesByLastUsed;
  nsTArray<CacheFileHandle*> mSpecialHandles;
  nsTArray<RefPtr<CacheFile> > mScheduledMetadataWrites;
  nsCOMPtr<nsITimer> mMetadataWritesTimer;
  bool mOverLimitEvicting{false};
  // When overlimit eviction is too slow and cache size reaches 105% of the
  // limit, this flag is set and no other content is cached to prevent
  // uncontrolled cache growing.
  bool mCacheSizeOnHardLimit{false};
  bool mRemovingTrashDirs{false};
  nsCOMPtr<nsITimer> mTrashTimer;
  nsCOMPtr<nsIFile> mTrashDir;
  nsCOMPtr<nsIDirectoryEnumerator> mTrashDirEnumerator;
  nsTArray<nsCString> mFailedTrashDirs;
  RefPtr<CacheFileContextEvictor> mContextEvictor;
  TimeStamp mLastSmartSizeTime;
};

}  // namespace net
}  // namespace mozilla

#endif