View Javadoc
1   /*
2    * Copyright (C) 2019, 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 org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
14  import org.eclipse.jgit.internal.storage.io.BlockSource;
15  import org.eclipse.jgit.internal.storage.pack.PackExt;
16  import org.eclipse.jgit.internal.storage.reftable.ReftableBatchRefUpdate;
17  import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
18  import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
19  import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
20  import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
21  import org.eclipse.jgit.lib.Ref;
22  import org.eclipse.jgit.transport.ReceiveCommand;
23  
24  import java.io.ByteArrayOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.List;
30  import java.util.Set;
31  
32  import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
33  
34  /**
35   * {@link org.eclipse.jgit.lib.BatchRefUpdate} for
36   * {@link org.eclipse.jgit.internal.storage.dfs.DfsReftableDatabase}.
37   */
38  public class DfsReftableBatchRefUpdate extends ReftableBatchRefUpdate {
39  	private static final int AVG_BYTES = 36;
40  
41  	private final DfsReftableDatabase refdb;
42  
43  	private final DfsObjDatabase odb;
44  
45  	/**
46  	 * Initialize batch update.
47  	 *
48  	 * @param refdb
49  	 *            database the update will modify.
50  	 * @param odb
51  	 *            object database to store the reftable.
52  	 */
53  	protected DfsReftableBatchRefUpdate(DfsReftableDatabase refdb,
54  			DfsObjDatabase odb) {
55  		super(refdb, refdb.reftableDatabase, refdb.getLock(), refdb.getRepository());
56  		this.refdb = refdb;
57  		this.odb = odb;
58  	}
59  
60  	@Override
61  	protected void applyUpdates(List<Ref> newRefs, List<ReceiveCommand> pending)
62  			throws IOException {
63  		Set<DfsPackDescription> prune = Collections.emptySet();
64  		DfsPackDescription pack = odb.newPack(PackSource.INSERT);
65  		try (DfsOutputStream out = odb.writeFile(pack, REFTABLE)) {
66  			ReftableConfig cfg = DfsPackCompactor
67  					.configureReftable(refdb.getReftableConfig(), out);
68  
69  			ReftableWriter.Stats stats;
70  			if (refdb.compactDuringCommit()
71  					&& newRefs.size() * AVG_BYTES <= cfg.getRefBlockSize()
72  					&& canCompactTopOfStack(cfg)) {
73  				ByteArrayOutputStream tmp = new ByteArrayOutputStream();
74  				ReftableWriter rw = new ReftableWriter(cfg, tmp);
75  				write(rw, newRefs, pending);
76  				rw.finish();
77  				stats = compactTopOfStack(out, cfg, tmp.toByteArray());
78  				prune = toPruneTopOfStack();
79  			} else {
80  				ReftableWriter rw = new ReftableWriter(cfg, out);
81  				write(rw, newRefs, pending);
82  				rw.finish();
83  				stats = rw.getStats();
84  			}
85  			pack.addFileExt(REFTABLE);
86  			pack.setReftableStats(stats);
87  		}
88  
89  		odb.commitPack(Collections.singleton(pack), prune);
90  		odb.addReftable(pack, prune);
91  		refdb.clearCache();
92  	}
93  
94  	private boolean canCompactTopOfStack(ReftableConfig cfg)
95  			throws IOException {
96  		refdb.getLock().lock();
97  		try {
98  			DfsReftableStack stack = refdb.stack();
99  			List<ReftableReader> readers = stack.readers();
100 			if (readers.isEmpty()) {
101 				return false;
102 			}
103 
104 			int lastIdx = readers.size() - 1;
105 			DfsReftable last = stack.files().get(lastIdx);
106 			DfsPackDescription desc = last.getPackDescription();
107 			if (desc.getPackSource() != PackSource.INSERT
108 				|| !packOnlyContainsReftable(desc)) {
109 				return false;
110 			}
111 
112 			ReftableReader table = readers.get(lastIdx);
113 			int bs = cfg.getRefBlockSize();
114 			return table.size() <= 3 * bs;
115 		} finally {
116 			refdb.getLock().unlock();
117 		}
118 	}
119 
120 	private ReftableWriter.Stats compactTopOfStack(OutputStream out,
121 			ReftableConfig cfg, byte[] newTable) throws IOException {
122 		refdb.getLock().lock();
123 		try {
124 			List<ReftableReader> stack = refdb.stack().readers();
125 
126 			ReftableReader last = stack.get(stack.size() - 1);
127 
128 			List<ReftableReader> tables = new ArrayList<>(2);
129 			tables.add(last);
130 			tables.add(new ReftableReader(BlockSource.from(newTable)));
131 
132 			ReftableCompactor compactor = new ReftableCompactor(out);
133 			compactor.setConfig(cfg);
134 			compactor.setIncludeDeletes(true);
135 			compactor.addAll(tables);
136 			compactor.compact();
137 			return compactor.getStats();
138 		} finally {
139 			refdb.getLock().unlock();
140 		}
141 	}
142 
143 	private Set<DfsPackDescription> toPruneTopOfStack() throws IOException {
144 		refdb.getLock().lock();
145 		try {
146 			List<DfsReftable> stack = refdb.stack().files();
147 
148 			DfsReftable last = stack.get(stack.size() - 1);
149 			return Collections.singleton(last.getPackDescription());
150 		} finally {
151 			refdb.getLock().unlock();
152 		}
153 	}
154 
155 	private boolean packOnlyContainsReftable(DfsPackDescription desc) {
156 		for (PackExt ext : PackExt.values()) {
157 			if (ext != REFTABLE && desc.hasFileExt(ext)) {
158 				return false;
159 			}
160 		}
161 		return true;
162 	}
163 }