View Javadoc
1   /*
2    * Copyright (C) 2017, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.dfs;
12  
13  import java.io.EOFException;
14  import java.io.IOException;
15  import java.nio.ByteBuffer;
16  import java.text.MessageFormat;
17  
18  import org.eclipse.jgit.errors.PackInvalidException;
19  import org.eclipse.jgit.internal.storage.pack.PackExt;
20  
21  /** Block based file stored in {@link DfsBlockCache}. */
22  abstract class BlockBasedFile {
23  	/** Cache that owns this file and its data. */
24  	final DfsBlockCache cache;
25  
26  	/** Unique identity of this file while in-memory. */
27  	final DfsStreamKey key;
28  
29  	/** Description of the associated pack file's storage. */
30  	final DfsPackDescription desc;
31  	final PackExt ext;
32  
33  	/**
34  	 * Preferred alignment for loading blocks from the backing file.
35  	 * <p>
36  	 * It is initialized to 0 and filled in on the first read made from the
37  	 * file. Block sizes may be odd, e.g. 4091, caused by the underling DFS
38  	 * storing 4091 user bytes and 5 bytes block metadata into a lower level
39  	 * 4096 byte block on disk.
40  	 */
41  	volatile int blockSize;
42  
43  	/**
44  	 * Total number of bytes in this pack file.
45  	 * <p>
46  	 * This field initializes to -1 and gets populated when a block is loaded.
47  	 */
48  	volatile long length;
49  
50  	/** True once corruption has been detected that cannot be worked around. */
51  	volatile boolean invalid;
52  
53  	/** Exception that caused the packfile to be flagged as invalid */
54  	protected volatile Exception invalidatingCause;
55  
56  	BlockBasedFile(DfsBlockCache cache, DfsPackDescription desc, PackExt ext) {
57  		this.cache = cache;
58  		this.key = desc.getStreamKey(ext);
59  		this.desc = desc;
60  		this.ext = ext;
61  	}
62  
63  	String getFileName() {
64  		return desc.getFileName(ext);
65  	}
66  
67  	boolean invalid() {
68  		return invalid;
69  	}
70  
71  	void setInvalid() {
72  		invalid = true;
73  	}
74  
75  	void setBlockSize(int newSize) {
76  		blockSize = newSize;
77  	}
78  
79  	long alignToBlock(long pos) {
80  		int size = blockSize;
81  		if (size == 0)
82  			size = cache.getBlockSize();
83  		return (pos / size) * size;
84  	}
85  
86  	int blockSize(ReadableChannel rc) {
87  		// If the block alignment is not yet known, discover it. Prefer the
88  		// larger size from either the cache or the file itself.
89  		int size = blockSize;
90  		if (size == 0) {
91  			size = rc.blockSize();
92  			if (size <= 0)
93  				size = cache.getBlockSize();
94  			else if (size < cache.getBlockSize())
95  				size = (cache.getBlockSize() / size) * size;
96  			blockSize = size;
97  		}
98  		return size;
99  	}
100 
101 	DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
102 		try (LazyChannel c = new LazyChannel(ctx, desc, ext)) {
103 			return cache.getOrLoad(this, pos, ctx, c);
104 		}
105 	}
106 
107 	DfsBlock readOneBlock(long pos, DfsReader ctx, ReadableChannel rc)
108 			throws IOException {
109 		if (invalid) {
110 			throw new PackInvalidException(getFileName(), invalidatingCause);
111 		}
112 
113 		ctx.stats.readBlock++;
114 		long start = System.nanoTime();
115 		try {
116 			int size = blockSize(rc);
117 			pos = (pos / size) * size;
118 
119 			// If the size of the file is not yet known, try to discover it.
120 			// Channels may choose to return -1 to indicate they don't
121 			// know the length yet, in this case read up to the size unit
122 			// given by the caller, then recheck the length.
123 			long len = length;
124 			if (len < 0) {
125 				len = rc.size();
126 				if (0 <= len)
127 					length = len;
128 			}
129 
130 			if (0 <= len && len < pos + size)
131 				size = (int) (len - pos);
132 			if (size <= 0)
133 				throw new EOFException(MessageFormat.format(
134 						DfsText.get().shortReadOfBlock, Long.valueOf(pos),
135 						getFileName(), Long.valueOf(0), Long.valueOf(0)));
136 
137 			byte[] buf = new byte[size];
138 			rc.position(pos);
139 			int cnt = read(rc, ByteBuffer.wrap(buf, 0, size));
140 			ctx.stats.readBlockBytes += cnt;
141 			if (cnt != size) {
142 				if (0 <= len) {
143 					throw new EOFException(MessageFormat.format(
144 							DfsText.get().shortReadOfBlock, Long.valueOf(pos),
145 							getFileName(), Integer.valueOf(size),
146 							Integer.valueOf(cnt)));
147 				}
148 
149 				// Assume the entire thing was read in a single shot, compact
150 				// the buffer to only the space required.
151 				byte[] n = new byte[cnt];
152 				System.arraycopy(buf, 0, n, 0, n.length);
153 				buf = n;
154 			} else if (len < 0) {
155 				// With no length at the start of the read, the channel should
156 				// have the length available at the end.
157 				length = len = rc.size();
158 			}
159 
160 			return new DfsBlock(key, pos, buf);
161 		} finally {
162 			ctx.stats.readBlockMicros += elapsedMicros(start);
163 		}
164 	}
165 
166 	static int read(ReadableChannel rc, ByteBuffer buf) throws IOException {
167 		int n;
168 		do {
169 			n = rc.read(buf);
170 		} while (0 < n && buf.hasRemaining());
171 		return buf.position();
172 	}
173 
174 	static long elapsedMicros(long start) {
175 		return (System.nanoTime() - start) / 1000L;
176 	}
177 
178 	/**
179 	 * A supplier of readable channel that opens the channel lazily.
180 	 */
181 	private static class LazyChannel
182 			implements AutoCloseable, DfsBlockCache.ReadableChannelSupplier {
183 		private final DfsReader ctx;
184 		private final DfsPackDescription desc;
185 		private final PackExt ext;
186 
187 		private ReadableChannel rc;
188 
189 		LazyChannel(DfsReader ctx, DfsPackDescription desc, PackExt ext) {
190 			this.ctx = ctx;
191 			this.desc = desc;
192 			this.ext = ext;
193 		}
194 
195 		@Override
196 		public ReadableChannel get() throws IOException {
197 			if (rc == null) {
198 				rc = ctx.db.openFile(desc, ext);
199 			}
200 			return rc;
201 		}
202 
203 		@Override
204 		public void close() throws IOException {
205 			if (rc != null) {
206 				rc.close();
207 			}
208 		}
209 	}
210 }