PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/bfs/DataServer.java

https://bitbucket.org/palvaro/netdb-bfs-branch
Java | 396 lines | 316 code | 50 blank | 30 comment | 48 complexity | dd278260f1d2c7731dbec45a23ef589c MD5 | raw file
  1. package bfs;
  2. import java.io.BufferedInputStream;
  3. import java.io.DataInputStream;
  4. import java.io.DataOutputStream;
  5. import java.io.EOFException;
  6. import java.io.File;
  7. import java.io.FileInputStream;
  8. import java.io.FileOutputStream;
  9. import java.io.IOException;
  10. import java.net.InetSocketAddress;
  11. import java.net.ServerSocket;
  12. import java.net.Socket;
  13. import java.nio.channels.ClosedByInterruptException;
  14. import java.nio.channels.ClosedChannelException;
  15. import java.nio.channels.FileChannel;
  16. import java.nio.channels.ServerSocketChannel;
  17. import java.nio.channels.SocketChannel;
  18. import java.util.ArrayList;
  19. import java.util.LinkedList;
  20. import java.util.List;
  21. import java.util.zip.CRC32;
  22. import java.util.zip.CheckedInputStream;
  23. public class DataServer extends Thread {
  24. /*
  25. * A DataWorker instance handles a single client connection, and runs in a
  26. * separate thread from the DataServer.
  27. */
  28. private class DataWorker extends Thread {
  29. private SocketChannel channel;
  30. private String clientAddr;
  31. private DataInputStream in;
  32. private DataOutputStream out;
  33. private volatile boolean inShutdown;
  34. DataWorker(SocketChannel channel, ThreadGroup group, int id) throws IOException {
  35. super(group, "DataWorker-" + id);
  36. channel.configureBlocking(true);
  37. this.channel = channel;
  38. Socket socket = channel.socket();
  39. this.clientAddr = socket.toString();
  40. this.in = new DataInputStream(socket.getInputStream());
  41. this.out = new DataOutputStream(socket.getOutputStream());
  42. this.inShutdown = false;
  43. }
  44. @Override
  45. public void run() {
  46. while (true) {
  47. try {
  48. try {
  49. readCommand();
  50. } catch (EOFException e) {
  51. this.channel.close();
  52. System.out.println("got EOF from " + this.clientAddr);
  53. break;
  54. }
  55. } catch (IOException e) {
  56. if (this.inShutdown)
  57. break;
  58. throw new RuntimeException(e);
  59. }
  60. }
  61. }
  62. public void shutdown() {
  63. if (this.inShutdown)
  64. return;
  65. this.inShutdown = true;
  66. // XXX: There doesn't appear to be a reasonable way to interrupt a
  67. // blocking read with old-style Java I/O (!). Therefore, just close
  68. // the socket, and catch/ignore the IOException in run().
  69. try {
  70. this.channel.close();
  71. } catch (IOException e) {
  72. throw new RuntimeException(e);
  73. }
  74. try {
  75. join();
  76. } catch (InterruptedException e) {
  77. throw new IllegalStateException("unexpected interrupt", e);
  78. }
  79. }
  80. private void readCommand() throws IOException {
  81. byte opCode = this.in.readByte();
  82. switch (opCode) {
  83. case DataProtocol.READ_OPERATION:
  84. doReadOperation();
  85. break;
  86. case DataProtocol.WRITE_OPERATION:
  87. doWriteOperation();
  88. break;
  89. case DataProtocol.DELETE_OPERATION:
  90. doDeleteOperation();
  91. break;
  92. default:
  93. throw new IOException("Unrecognized opcode: " + opCode + " from " + this.clientAddr) ;
  94. }
  95. }
  96. private void doWriteOperation() throws IOException {
  97. int chunkId = this.in.readInt();
  98. List<String> path = readHeaders();
  99. int dataLength = this.in.readInt();
  100. long startTime = System.currentTimeMillis();
  101. File chunkFile = createChunkFile(chunkId);
  102. FileChannel fc = new FileOutputStream(chunkFile).getChannel();
  103. long offset = 0;
  104. while (true) {
  105. long nread = fc.transferFrom(this.channel, offset, dataLength - offset);
  106. offset += nread;
  107. if (offset == dataLength)
  108. break;
  109. else if (offset > dataLength)
  110. throw new IllegalStateException();
  111. }
  112. fc.close();
  113. System.out.println("Wrote chunk file " + chunkId + " (length " +
  114. chunkFile.length() + ") in " +
  115. (System.currentTimeMillis() - startTime) + " msec");
  116. if (chunkFile.length() != dataLength)
  117. throw new RuntimeException("Unexpected file length: expected " +
  118. dataLength + ", got " + chunkFile.length());
  119. startTime = System.currentTimeMillis();
  120. createCRCFile(chunkId, getFileChecksum(chunkFile));
  121. System.out.println("Wrote CRC file for " + chunkId + " in " +
  122. (System.currentTimeMillis() - startTime) + " msec");
  123. // we are not pipelining yet.
  124. if (path.size() > 0) {
  125. System.out.println("Path size was " + path.size());
  126. copyToNext(chunkFile, chunkId, path);
  127. }
  128. }
  129. private void copyToNext(File f, int chunkId, List<String> path) throws IOException {
  130. DataConnection conn = new DataConnection(path);
  131. conn.sendRoutingData(chunkId);
  132. FileChannel fc = new FileInputStream(f).getChannel();
  133. conn.write(fc, (int) f.length());
  134. fc.close();
  135. conn.close();
  136. }
  137. private void doDeleteOperation() throws IOException {
  138. int chunkId = this.in.readInt();
  139. File victim = getChunkFile(chunkId);
  140. victim.delete();
  141. }
  142. private List<String> readHeaders() throws IOException {
  143. // XXX: clean this up later
  144. String[] path = null;
  145. int sourceRouteListLen = this.in.readInt();
  146. if (sourceRouteListLen > 0) {
  147. path = new String[sourceRouteListLen + 1];
  148. int i = 0;
  149. path[0] = "";
  150. for (char b = this.in.readChar(); b != ';' && i < sourceRouteListLen; b = this.in.readChar()) {
  151. if (b == '|') {
  152. i++;
  153. path[i] = "";
  154. } else {
  155. path[i] = path[i] + b;
  156. }
  157. }
  158. } else {
  159. if (this.in.readChar() != ';') {
  160. throw new RuntimeException("invalid header");
  161. }
  162. }
  163. List<String> ret = new LinkedList<String>();
  164. for (int i = 0; i < sourceRouteListLen; i++) {
  165. ret.add(path[i]);
  166. }
  167. return ret;
  168. }
  169. private void doReadOperation() throws IOException {
  170. int chunkId = this.in.readInt();
  171. File chunkFile = getChunkFile(chunkId);
  172. // If we don't have the chunk, return failure to the client
  173. if (chunkFile == null) {
  174. this.out.writeBoolean(false);
  175. return;
  176. }
  177. // Otherwise, return success, then the length + contents
  178. this.out.writeBoolean(true);
  179. int fileSize = (int) chunkFile.length();
  180. this.out.writeInt(fileSize);
  181. long cksum = getFileChecksum(chunkFile);
  182. long expectedCksum = getCRCFromFile(chunkId);
  183. if (cksum != expectedCksum) {
  184. // what should we do? presumably, signal the master to delete
  185. // the bad chunk
  186. throw new RuntimeException("file checksums didn't match");
  187. }
  188. FileChannel fc = new FileInputStream(chunkFile).getChannel();
  189. long offset = 0;
  190. while (true) {
  191. long nwrite = fc.transferTo(offset, fileSize - offset, this.channel);
  192. offset += nwrite;
  193. if (offset == fileSize)
  194. break;
  195. else if (offset > fileSize)
  196. throw new IllegalStateException();
  197. }
  198. fc.close();
  199. }
  200. }
  201. private File fsRoot;
  202. private ThreadGroup workers;
  203. private int nextWorkerId;
  204. private ServerSocketChannel listener;
  205. private volatile boolean inShutdown;
  206. DataServer(int port, File fsRoot) {
  207. this.fsRoot = fsRoot;
  208. this.workers = new ThreadGroup("DataWorkers");
  209. this.nextWorkerId = 0;
  210. this.inShutdown = false;
  211. try {
  212. this.listener = ServerSocketChannel.open();
  213. ServerSocket serverSocket = this.listener.socket();
  214. serverSocket.setReuseAddress(true);
  215. InetSocketAddress listenAddr = new InetSocketAddress(port);
  216. serverSocket.bind(listenAddr);
  217. } catch (IOException e) {
  218. throw new RuntimeException(e);
  219. }
  220. }
  221. public void shutdown() {
  222. if (this.inShutdown)
  223. return;
  224. // Notify the DataServer thread that it should shutdown; we stop
  225. // accepting new connections at this point
  226. this.inShutdown = true;
  227. interrupt();
  228. try {
  229. join();
  230. } catch (InterruptedException e) {
  231. throw new IllegalStateException("unexpected interrupt", e);
  232. }
  233. // Interrupt each active worker thread and wait for it to exit
  234. Thread[] threadList = new Thread[this.workers.activeCount()];
  235. this.workers.enumerate(threadList);
  236. for (Thread t : threadList) {
  237. DataWorker dw = (DataWorker) t;
  238. dw.shutdown();
  239. }
  240. this.workers.destroy();
  241. }
  242. @Override
  243. public void run() {
  244. while (true) {
  245. try {
  246. if (this.inShutdown) {
  247. System.out.println("Got shutdown request");
  248. // shutdown() notifies us that we should shutdown by setting
  249. // the "inShutdown" field, and then calling interrupt(). If
  250. // we're inside accept() at the time, interrupt() auto-closes
  251. // the socket; otherwise, we need to do so by hand.
  252. if (this.listener.isOpen())
  253. this.listener.close();
  254. break;
  255. }
  256. SocketChannel channel = this.listener.accept();
  257. DataWorker dw = new DataWorker(channel, this.workers,
  258. this.nextWorkerId++);
  259. dw.start();
  260. } catch (ClosedByInterruptException e) {
  261. if (!this.inShutdown)
  262. throw new IllegalStateException("unexpected interrupt", e);
  263. // Otherwise, just rerun the loop and then exit
  264. } catch (ClosedChannelException e) {
  265. throw new RuntimeException(e);
  266. } catch (IOException e) {
  267. throw new RuntimeException(e);
  268. }
  269. }
  270. }
  271. public void createCRCFile(int chunkId, long checksum) throws IOException {
  272. String filename = this.fsRoot + File.separator + "checksums" + File.separator
  273. + chunkId + ".cksum";
  274. File newf = new File(filename);
  275. if (!newf.createNewFile()) {
  276. throw new RuntimeException("Failed to create fresh file " + filename);
  277. }
  278. DataOutputStream fos = new DataOutputStream(new FileOutputStream(newf));
  279. fos.writeLong(checksum);
  280. fos.close();
  281. }
  282. public long getCRCFromFile(int chunkId) throws IOException {
  283. String filename = this.fsRoot + File.separator + "checksums" + File.separator
  284. + chunkId + ".cksum";
  285. File newf = new File(filename);
  286. if (!newf.exists())
  287. throw new RuntimeException("Failed to find checksum "+ filename);
  288. DataInputStream dis = new DataInputStream(new FileInputStream(newf));
  289. long cLong = dis.readLong();
  290. dis.close();
  291. return cLong;
  292. }
  293. private long getFileChecksum(File file) throws IOException {
  294. FileInputStream s = new FileInputStream(file);
  295. CheckedInputStream check = new CheckedInputStream(s, new CRC32());
  296. BufferedInputStream in = new BufferedInputStream(check);
  297. byte[] tmpBuf = new byte[8192];
  298. while (in.read(tmpBuf, 0, tmpBuf.length) != -1) {
  299. }
  300. return check.getChecksum().getValue();
  301. }
  302. public File createChunkFile(int chunkId) throws IOException {
  303. String filename = this.fsRoot + File.separator + "chunks" + File.separator
  304. + chunkId;
  305. File newf = new File(filename);
  306. if (!newf.createNewFile()) {
  307. File d = new File(this.fsRoot + File.separator + "chunks");
  308. for (String f : d.list()) {
  309. System.out.println("\tFILE: " + f);
  310. }
  311. throw new RuntimeException("Failed to create fresh file " + filename);
  312. }
  313. return newf;
  314. }
  315. public File getChunkFile(int chunkId) {
  316. String filename = this.fsRoot + File.separator + "chunks" + File.separator
  317. + chunkId;
  318. File file = new File(filename);
  319. if (!file.exists())
  320. return null;
  321. if (!file.isFile())
  322. throw new RuntimeException("Chunk file is not a regular file: " + file);
  323. return file;
  324. }
  325. /**
  326. * This function is periodically invoked by the Overlog code; it returns a
  327. * list of the chunk files stored at this data node. Before returning a
  328. * chunk file, we check that the checksum file for that chunk already
  329. * exists. This avoids a race condition: we don't want to let Overlog-land
  330. * know that a file exists until it has been completely written to disk.
  331. * Since we write the checksum file after we've finished writing the file
  332. * itself, this check avoids the race.
  333. */
  334. public static List<File> dirListing(File dir) {
  335. File fDir = new File(dir, "chunks");
  336. File cDir = new File(dir, "checksums");
  337. List<File> ret = new ArrayList<File>();
  338. for (File f : fDir.listFiles()) {
  339. File csum = new File(cDir, f.getName() + ".cksum");
  340. if (csum.exists()) {
  341. ret.add(f);
  342. }
  343. }
  344. return ret;
  345. }
  346. }