aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/main/java/com/google/common/jimfs/HeapDisk.java
blob: ab06933f6791a6c61ba0a141ccb8defb9b7498a3 (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
/*
 * Copyright 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.jimfs;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.math.LongMath;
import java.io.IOException;
import java.math.RoundingMode;

/**
 * A resizable pseudo-disk acting as a shared space for storing file data. A disk allocates fixed
 * size blocks of bytes to files as needed and may cache blocks that have been freed for reuse. A
 * memory disk has a fixed maximum number of blocks it will allocate at a time (which sets the total
 * "size" of the disk) and a maximum number of unused blocks it will cache for reuse at a time
 * (which sets the minimum amount of space the disk will use once
 *
 * @author Colin Decker
 */
final class HeapDisk {

  /** Fixed size of each block for this disk. */
  private final int blockSize;

  /** Maximum total number of blocks that the disk may contain at any time. */
  private final int maxBlockCount;

  /** Maximum total number of unused blocks that may be cached for reuse at any time. */
  private final int maxCachedBlockCount;

  /**
   * Cache of free blocks to be allocated to files. While this is stored as a file, it isn't used
   * like a normal file: only the methods for accessing its blocks are used.
   */
  @VisibleForTesting final RegularFile blockCache;

  /** The current total number of blocks that are currently allocated to files. */
  private int allocatedBlockCount;

  /** Creates a new disk using settings from the given configuration. */
  public HeapDisk(Configuration config) {
    this.blockSize = config.blockSize;
    this.maxBlockCount = toBlockCount(config.maxSize, blockSize);
    this.maxCachedBlockCount =
        config.maxCacheSize == -1 ? maxBlockCount : toBlockCount(config.maxCacheSize, blockSize);
    this.blockCache = createBlockCache(maxCachedBlockCount);
  }

  /**
   * Creates a new disk with the given {@code blockSize}, {@code maxBlockCount} and {@code
   * maxCachedBlockCount}.
   */
  public HeapDisk(int blockSize, int maxBlockCount, int maxCachedBlockCount) {
    checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize);
    checkArgument(maxBlockCount > 0, "maxBlockCount (%s) must be positive", maxBlockCount);
    checkArgument(
        maxCachedBlockCount >= 0, "maxCachedBlockCount must be non-negative", maxCachedBlockCount);
    this.blockSize = blockSize;
    this.maxBlockCount = maxBlockCount;
    this.maxCachedBlockCount = maxCachedBlockCount;
    this.blockCache = createBlockCache(maxCachedBlockCount);
  }

  /** Returns the nearest multiple of {@code blockSize} that is <= {@code size}. */
  private static int toBlockCount(long size, int blockSize) {
    return (int) LongMath.divide(size, blockSize, RoundingMode.FLOOR);
  }

  private RegularFile createBlockCache(int maxCachedBlockCount) {
    return new RegularFile(-1, this, new byte[Math.min(maxCachedBlockCount, 8192)][], 0, 0);
  }

  /** Returns the size of blocks created by this disk. */
  public int blockSize() {
    return blockSize;
  }

  /**
   * Returns the total size of this disk. This is the maximum size of the disk and does not reflect
   * the amount of data currently allocated or cached.
   */
  public synchronized long getTotalSpace() {
    return maxBlockCount * (long) blockSize;
  }

  /**
   * Returns the current number of unallocated bytes on this disk. This is the maximum number of
   * additional bytes that could be allocated and does not reflect the number of bytes currently
   * actually cached in the disk.
   */
  public synchronized long getUnallocatedSpace() {
    return (maxBlockCount - allocatedBlockCount) * (long) blockSize;
  }

  /** Allocates the given number of blocks and adds them to the given file. */
  public synchronized void allocate(RegularFile file, int count) throws IOException {
    int newAllocatedBlockCount = allocatedBlockCount + count;
    if (newAllocatedBlockCount > maxBlockCount) {
      throw new IOException("out of disk space");
    }

    int newBlocksNeeded = Math.max(count - blockCache.blockCount(), 0);

    for (int i = 0; i < newBlocksNeeded; i++) {
      file.addBlock(new byte[blockSize]);
    }

    if (newBlocksNeeded != count) {
      blockCache.transferBlocksTo(file, count - newBlocksNeeded);
    }

    allocatedBlockCount = newAllocatedBlockCount;
  }

  /** Frees all blocks in the given file. */
  public void free(RegularFile file) {
    free(file, file.blockCount());
  }

  /** Frees the last {@code count} blocks from the given file. */
  public synchronized void free(RegularFile file, int count) {
    int remainingCacheSpace = maxCachedBlockCount - blockCache.blockCount();
    if (remainingCacheSpace > 0) {
      file.copyBlocksTo(blockCache, Math.min(count, remainingCacheSpace));
    }
    file.truncateBlocks(file.blockCount() - count);

    allocatedBlockCount -= count;
  }
}