s243a

FECCodec.java (freenet.client)

Oct 22nd, 2014
510
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 16.70 KB | None | 0 0
  1. // s243a pearltree node: http://www.pearltrees.com/s243a/feccodec-java-freenet-client/id12827811
  2. /* This code is part of Freenet. It is distributed under the GNU General
  3.  * Public License, version 2 (or at your option any later version). See
  4.  * http://www.gnu.org/ for further details of the GPL. */
  5. package freenet.client;
  6.  
  7. import java.io.DataInputStream;
  8. import java.io.IOException;
  9. import java.io.OutputStream;
  10.  
  11. import com.db4o.ObjectContainer;
  12. import com.onionnetworks.fec.FECCode;
  13. import com.onionnetworks.util.Buffer;
  14.  
  15. import freenet.client.InsertContext.CompatibilityMode;
  16. import freenet.support.LogThresholdCallback;
  17. import freenet.support.Logger;
  18. import freenet.support.Logger.LogLevel;
  19. import freenet.support.api.Bucket;
  20. import freenet.support.api.BucketFactory;
  21. import freenet.support.io.Closer;
  22.  
  23. /**
  24.  * FEC (forward error correction) handler.
  25.  * I didn't keep the old freenet.client.FEC* etc as it seemed grossly overengineered with
  26.  * a lot of code there only because of API confusion.
  27.  * @author root
  28.  *
  29.  */
  30. public abstract class FECCodec {
  31.  
  32.     protected transient FECCode fec;
  33.     protected final int k, n;
  34.     // Striping is very costly I/O wise.
  35.     // So set a maximum buffer size and calculate the stripe size accordingly.
  36.     static final int MAX_MEMORY_BUFFER = 8*1024*1024;
  37.  
  38.         private static volatile boolean logMINOR;
  39.     static {
  40.         Logger.registerLogThresholdCallback(new LogThresholdCallback(){
  41.             @Override
  42.             public void shouldUpdate(){
  43.                 logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
  44.             }
  45.         });
  46.     }
  47.  
  48.     protected abstract void loadFEC();
  49.    
  50.     protected FECCodec(int k, int n) {
  51.         this.k = k;
  52.         this.n = n;
  53.         if(n == 0 || n < k)
  54.             throw new IllegalArgumentException("Invalid: k="+k+" n="+n);
  55.     }
  56.    
  57.     /**
  58.      * Get a codec where we know both the number of data blocks and the number
  59.      * of check blocks, and the codec type. Normally for decoding.
  60.      */
  61.     public static FECCodec getCodec(short splitfileType, int dataBlocks, int checkBlocks) {
  62.         if(logMINOR)
  63.             Logger.minor(FECCodec.class, "getCodec: splitfileType="+splitfileType+" dataBlocks="+dataBlocks+" checkBlocks="+checkBlocks);
  64.         if(splitfileType == Metadata.SPLITFILE_NONREDUNDANT)
  65.             return null;
  66.         if(splitfileType == Metadata.SPLITFILE_ONION_STANDARD)
  67.             return StandardOnionFECCodec.getInstance(dataBlocks, checkBlocks);
  68.         else
  69.             return null;
  70.     }
  71.  
  72.     /**
  73.      * Get a codec where we know only the number of data blocks and the codec
  74.      * type. Normally for encoding.
  75.      */
  76.     public static FECCodec getCodec(short splitfileType, int dataBlocks, CompatibilityMode compatibilityMode) {
  77.         if(splitfileType == Metadata.SPLITFILE_NONREDUNDANT)
  78.             return null;
  79.         if(splitfileType == Metadata.SPLITFILE_ONION_STANDARD) {
  80.             int checkBlocks = standardOnionCheckBlocks(dataBlocks, compatibilityMode);
  81.             return StandardOnionFECCodec.getInstance(dataBlocks, checkBlocks);
  82.         }
  83.         else
  84.             return null;
  85.     }
  86.    
  87.     private static int standardOnionCheckBlocks(int dataBlocks, CompatibilityMode compatibilityMode) {
  88.         /**
  89.          * ALCHEMY: What we do know is that redundancy by FEC is much more efficient than
  90.          * redundancy by simply duplicating blocks, for obvious reasons (see e.g. Wuala). But
  91.          * we have to have some redundancy at the duplicating blocks level because we do use
  92.          * some keys directly etc: we store an insert in 3 nodes. We also cache it on 20 nodes,
  93.          * but generally the key will fall out of the caches within days. So long term, it's 3.
  94.          * Multiplied by 2 here, makes 6. Used to be 1.5 * 3 = 4.5. Wuala uses 5, but that's
  95.          * all FEC.
  96.          */
  97.         int checkBlocks = dataBlocks * HighLevelSimpleClientImpl.SPLITFILE_CHECK_BLOCKS_PER_SEGMENT / HighLevelSimpleClientImpl.SPLITFILE_SCALING_BLOCKS_PER_SEGMENT;
  98.         if(dataBlocks >= HighLevelSimpleClientImpl.SPLITFILE_CHECK_BLOCKS_PER_SEGMENT)
  99.             checkBlocks = HighLevelSimpleClientImpl.SPLITFILE_CHECK_BLOCKS_PER_SEGMENT;
  100.         // An extra block for anything below the limit.
  101.         checkBlocks++;
  102.         // Keep it within 256 blocks.
  103.         if(dataBlocks < 256 && dataBlocks + checkBlocks > 256)
  104.             checkBlocks = 256 - dataBlocks;
  105.         if(compatibilityMode == InsertContext.CompatibilityMode.COMPAT_1250 || compatibilityMode == InsertContext.CompatibilityMode.COMPAT_1250_EXACT) {
  106.             // Pre-1250, redundancy was always 100% or less.
  107.             // Builds of that period using the native FEC (ext #26) will segfault sometimes on >100% redundancy.
  108.             // So limit check blocks to data blocks.
  109.             if(checkBlocks > dataBlocks) checkBlocks = dataBlocks;
  110.         }
  111.         return checkBlocks;
  112.     }
  113.  
  114.     public static int getCheckBlocks(short splitfileType, int dataBlocks, CompatibilityMode compatibilityMode) {
  115.         if(splitfileType == Metadata.SPLITFILE_ONION_STANDARD) {
  116.             return standardOnionCheckBlocks(dataBlocks, compatibilityMode);
  117.         } else
  118.             return 0;
  119.     }
  120.  
  121.     /**
  122.      * How many check blocks?
  123.      */
  124.     public abstract int countCheckBlocks();
  125.  
  126.     protected void realDecode(SplitfileBlock[] dataBlockStatus, SplitfileBlock[] checkBlockStatus, int blockLength, BucketFactory bf) throws IOException {
  127.         loadFEC();
  128.         if(logMINOR)
  129.             Logger.minor(this, "Doing decode: " + dataBlockStatus.length + " data blocks, " + checkBlockStatus.length + " check blocks, block length " + blockLength + " with " + this, new Exception("debug"));
  130.         if(dataBlockStatus.length + checkBlockStatus.length != n)
  131.             throw new IllegalArgumentException();
  132.         if(dataBlockStatus.length != k)
  133.             throw new IllegalArgumentException();
  134.         Buffer[] packets = new Buffer[k];
  135.         Bucket[] buckets = new Bucket[n];
  136.         DataInputStream[] readers = new DataInputStream[n];
  137.         OutputStream[] writers = new OutputStream[k];
  138.         boolean[] toWrite = new boolean[k];
  139.        
  140.         int stripeSize = MAX_MEMORY_BUFFER / k;
  141.         if(stripeSize > blockLength)
  142.             stripeSize = blockLength;
  143.         // Must be even if 16-bit code.
  144.         if((k > 256 || n > 256) && ((stripeSize & 1) == 1))
  145.             stripeSize++;
  146.         if(logMINOR && stripeSize != 32768) Logger.minor(this, "Stripe size is "+stripeSize);
  147.  
  148.         try {
  149.  
  150.             byte[] realBuffer = new byte[k * stripeSize];
  151.  
  152.             // Indicates where the data is for each block. Many of the blocks won't have any data.
  153.             int[] packetIndexes = new int[k];
  154.             for(int i = 0; i < packetIndexes.length; i++)
  155.                 packetIndexes[i] = -1;
  156.  
  157.             int idx = 0;
  158.  
  159.             for(int i = 0; i < k; i++)
  160.                 packets[i] = new Buffer(realBuffer, i * stripeSize,
  161.                     stripeSize);
  162.  
  163.             // Due to the not-fetching-last-block code, we need to check here.
  164.            
  165.             boolean needDecode = false;
  166.             for(int i = 0; i < dataBlockStatus.length;i++) {
  167.                 if(dataBlockStatus[i].getData() == null) needDecode = true;
  168.             }
  169.            
  170.             if(!needDecode) return;
  171.            
  172.             for(int i = 0; i < dataBlockStatus.length; i++) {
  173.                 buckets[i] = dataBlockStatus[i].getData();
  174.                 if(buckets[i] == null) {
  175.                     buckets[i] = bf.makeBucket(blockLength);
  176.                     if(stripeSize != blockLength) {
  177.                         writers[i] = buckets[i].getOutputStream();
  178.                     }
  179.                     toWrite[i] = true;
  180.                     if(logMINOR)
  181.                         Logger.minor(this, "writers[" + i + "] != null");
  182.                     readers[i] = null;
  183.                 }
  184.                 else {
  185.                     long sz = buckets[i].size();
  186.                     if(sz < blockLength) {
  187.                         if(i != dataBlockStatus.length - 1)
  188.                             throw new IllegalArgumentException("All buckets must be the full size (caller must pad if needed) but data bucket " + i + " of " + dataBlockStatus.length + " (" + dataBlockStatus[i] + ") is " + sz + " not " + blockLength);
  189.                     } else {
  190.                         if(logMINOR)
  191.                             Logger.minor(this, "writers[" + i + "] = null (already filled)");
  192.                         writers[i] = null;
  193.                         readers[i] = new DataInputStream(buckets[i].getInputStream());
  194.                         packetIndexes[idx++] = i;
  195.                     }
  196.                 }
  197.             }
  198.             for(int i = 0; i < checkBlockStatus.length; i++) {
  199.                 buckets[i + k] = checkBlockStatus[i].getData();
  200.                 if(buckets[i + k] == null) {
  201.                     readers[i + k] = null;
  202.                 } else {
  203.                     if(stripeSize != blockLength) {
  204.                         readers[i + k] = new DataInputStream(buckets[i + k].getInputStream());
  205.                     }
  206.                     if(idx < k) {
  207.                         packetIndexes[idx++] = i + k;
  208.                     }
  209.                 }
  210.             }
  211.  
  212.             if(idx < k)
  213.                 throw new IllegalArgumentException("Must have at least k packets (k=" + k + ",idx=" + idx + ')');
  214.  
  215.             if(logMINOR)
  216.                 for(int i = 0; i < packetIndexes.length; i++)
  217.                     Logger.minor(this, "[" + i + "] = " + packetIndexes[i]);
  218.  
  219.             for(int offset = 0; offset < blockLength; offset += stripeSize) {
  220.                 if(offset + stripeSize > blockLength) {
  221.                     stripeSize = blockLength - offset;
  222.                 }
  223.                 // Read the data in first
  224.                 for(int i = 0; i < k; i++) {
  225.                     int x = packetIndexes[i];
  226.                     DataInputStream dis;
  227.                     if(stripeSize == blockLength)
  228.                         dis = new DataInputStream(buckets[x].getInputStream());
  229.                     else
  230.                         dis = readers[x];
  231.                     try {
  232.                         dis.readFully(realBuffer, i * stripeSize,
  233.                                 stripeSize);
  234.                     } finally {
  235.                         if(stripeSize == blockLength) {
  236.                             dis.close();
  237.                         }
  238.                     }
  239.                 }
  240.                 // The FEC codec will change the indexes in disposableIndexes. We need them to stay the same for multiple stripes.
  241.                 int[] disposableIndexes = packetIndexes.clone();
  242.                 // Do the decode, not shuffled
  243.                 fec.decode(packets, disposableIndexes);
  244.                 // packets now contains an array of decoded blocks, in order
  245.                 // Write the data out
  246.                 for(int i = 0; i < k; i++) {
  247.                     if(toWrite[i]) {
  248.                         OutputStream os;
  249.                         if(stripeSize == blockLength) {
  250.                             os = buckets[i].getOutputStream();
  251.                         } else {
  252.                             os = writers[i];
  253.                         }
  254.                         try {
  255.                             os.write(realBuffer, i * stripeSize,
  256.                                     stripeSize);
  257.                         } finally {
  258.                             if(stripeSize == blockLength) {
  259.                                 os.close();
  260.                             }
  261.                         }
  262.                     }
  263.                 }
  264.             }
  265.         }
  266.         finally {
  267.             for(int i = 0; i < k; i++)
  268.                 Closer.close(writers[i]);
  269.             for(int i = 0; i < n; i++)
  270.                 Closer.close(readers[i]);
  271.         }
  272.         // Set new buckets only after have a successful decode.
  273.         // Note that the last data bucket will be overwritten padded.
  274.         for(int i = 0; i < dataBlockStatus.length; i++) {
  275.             Bucket data = buckets[i];
  276.             if(data.size() != blockLength)
  277.                 throw new IllegalStateException("Block " + i + ": " + data + " : " + dataBlockStatus[i] + " length " + data.size() + " whereas blockLength="+blockLength);
  278.             Bucket existingData = dataBlockStatus[i].trySetData(data);
  279.             if(existingData != null && existingData != data) {
  280.                 if(logMINOR) Logger.minor(this, "Discarding block "+i+" as now unneeded");
  281.                 data.free();
  282.             }
  283.         }
  284.     }
  285.  
  286.     /**
  287.      * Do the actual encode.
  288.      */
  289.     protected void realEncode(Bucket[] dataBlockStatus,
  290.         Bucket[] checkBlockStatus, int blockLength, BucketFactory bf)
  291.         throws IOException {
  292.         if(bf == null) throw new NullPointerException();
  293.         loadFEC();
  294.         //      Runtime.getRuntime().gc();
  295. //      Runtime.getRuntime().runFinalization();
  296. //      Runtime.getRuntime().gc();
  297. //      Runtime.getRuntime().runFinalization();
  298.         if(logMINOR) {
  299.             long memUsedAtStart = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
  300.             Logger.minor(this, "Memory in use at start: " + memUsedAtStart + " max=" + Runtime.getRuntime().maxMemory());
  301.             Logger.minor(this, "Doing encode: " + dataBlockStatus.length + " data blocks, " + checkBlockStatus.length + " check blocks, block length " + blockLength + " with " + this);
  302.         }
  303.         if((dataBlockStatus.length + checkBlockStatus.length != n) ||
  304.             (dataBlockStatus.length != k))
  305.             throw new IllegalArgumentException("Data blocks: " + dataBlockStatus.length + ", Check blocks: " + checkBlockStatus.length + ", n: " + n + ", k: " + k);
  306.         Buffer[] dataPackets = new Buffer[k];
  307.         Buffer[] checkPackets;
  308.         Bucket[] buckets = new Bucket[n];
  309.         DataInputStream[] readers = new DataInputStream[k];
  310.         OutputStream[] writers = null;
  311.        
  312.         try {
  313.  
  314.             int[] toEncode;
  315.             int numberToEncode = 0; // can be less than n-k
  316.  
  317.             int created = 0;
  318.             for(int i = 0; i < checkBlockStatus.length; i++) {
  319.                 buckets[i + k] = checkBlockStatus[i];
  320.                 if(buckets[i + k] == null) {
  321.                     buckets[i + k] = bf.makeBucket(blockLength);
  322.                     numberToEncode++;
  323.                     created++;
  324.                 }
  325.             }
  326.            
  327.             toEncode = new int[numberToEncode];
  328.             checkPackets = new Buffer[numberToEncode];
  329.             writers = new OutputStream[numberToEncode];
  330.            
  331.             int stripeSize = MAX_MEMORY_BUFFER / (k + numberToEncode);
  332.             if(stripeSize > blockLength)
  333.                 stripeSize = blockLength;
  334.             // Must be even if 16-bit code.
  335.             if((k > 256 || n > 256) && ((stripeSize & 1) == 1))
  336.                 stripeSize++;
  337.             if(logMINOR && stripeSize != 32768) Logger.minor(this, "Stripe size is "+stripeSize);
  338.  
  339.             byte[] realBuffer = new byte[(k + numberToEncode) * stripeSize];
  340.            
  341.             int x = 0;
  342.             for(int i = 0; i < checkBlockStatus.length; i++) {
  343.                 if(checkBlockStatus[i] == null) {
  344.                     toEncode[x] = i + k;
  345.                     checkPackets[x] = new Buffer(realBuffer, (x + k) * stripeSize, stripeSize);
  346.                     if(stripeSize != blockLength)
  347.                         writers[x] = buckets[i + k].getOutputStream();
  348.                     x++;
  349.                 }
  350.             }
  351.            
  352.             for(int i = 0; i < k; i++)
  353.                 dataPackets[i] = new Buffer(realBuffer, i * stripeSize,
  354.                     stripeSize);
  355.  
  356.             for(int i = 0; i < dataBlockStatus.length; i++) {
  357.                 buckets[i] = dataBlockStatus[i];
  358.                 if(buckets[i] == null)
  359.                     throw new NullPointerException("Data bucket "+i+" is null!");
  360.                 long sz = buckets[i].size();
  361.                 if(sz < blockLength) {
  362.                     throw new IllegalArgumentException("All buckets must be the full size: caller must pad the last one if needed");
  363.                 }
  364.                 if(stripeSize != blockLength)
  365.                     readers[i] = new DataInputStream(buckets[i].getInputStream());
  366.             }
  367.  
  368.             if(logMINOR)
  369.                 Logger.minor(this, "Created "+created+" check buckets");
  370.  
  371.             //          Runtime.getRuntime().gc();
  372. //          Runtime.getRuntime().runFinalization();
  373. //          Runtime.getRuntime().gc();
  374. //          Runtime.getRuntime().runFinalization();
  375.             if(logMINOR) {
  376.                 long memUsedBeforeEncodes = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
  377.                 Logger.minor(this, "Memory in use before encodes: " + memUsedBeforeEncodes);
  378.             }
  379.  
  380.             if(numberToEncode > 0)
  381.                 // Do the (striped) encode
  382.  
  383.                 for(int offset = 0; offset < blockLength; offset += stripeSize) {
  384.                     if(offset + stripeSize > blockLength)
  385.                         stripeSize = blockLength - offset;
  386.                     if(logMINOR) {
  387.                         long memUsedBeforeRead = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
  388.                         Logger.minor(this, "Memory in use before read: " + memUsedBeforeRead);
  389.                     }
  390.                     // Read the data in first
  391.                     for(int i = 0; i < k; i++) {
  392.                         DataInputStream dis;
  393.                         if(stripeSize == blockLength)
  394.                             dis = new DataInputStream(buckets[i].getInputStream());
  395.                         else
  396.                             dis = readers[i];
  397.                         try {
  398.                         dis.readFully(realBuffer, i * stripeSize,
  399.                             stripeSize);
  400.                         } finally {
  401.                         if(stripeSize == blockLength)
  402.                             dis.close();
  403.                         }
  404.                     }
  405.                     // Do the encode
  406.                     // Not shuffled
  407.                     long startTime = 0;
  408.                     if(logMINOR) {
  409.                         startTime = System.currentTimeMillis();
  410.                         long memUsedBeforeStripe = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
  411.                         Logger.minor(this, "Memory in use before stripe: " + memUsedBeforeStripe);
  412.                     }
  413.                     fec.encode(dataPackets, checkPackets, toEncode);
  414.                     if(logMINOR) {
  415.                         long memUsedAfterStripe = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
  416.                         Logger.minor(this, "Memory in use after stripe: " + memUsedAfterStripe);
  417.                         long endTime = System.currentTimeMillis();
  418.                         Logger.minor(this, "Stripe encode took " + (endTime - startTime) + "ms for k=" + k + ", n=" + n + ", stripeSize=" + stripeSize);
  419.                     }
  420.                     // packets now contains an array of decoded blocks, in order
  421.                     // Write the data out
  422.                     for(int i = 0; i < writers.length; i++) {
  423.                         OutputStream os;
  424.                         if(stripeSize == blockLength)
  425.                             os = buckets[toEncode[i]].getOutputStream();
  426.                         else
  427.                             os = writers[i];
  428.                         try {
  429.                         os.write(realBuffer, (i + k) * stripeSize, stripeSize);
  430.                         } finally {
  431.                         if(stripeSize == blockLength)
  432.                             os.close();
  433.                         }
  434.                     }
  435.                 }
  436.  
  437.         }
  438.         finally {
  439.             for(int i = 0; i < k; i++)
  440.                 Closer.close(readers[i]);
  441.             if(writers != null) {
  442.                 for(int i = 0; i < writers.length; i++)
  443.                     Closer.close(writers[i]);
  444.             }
  445.         }
  446.         // Set new buckets only after have a successful decode.
  447.         for(int i = 0; i < checkBlockStatus.length; i++) {
  448.             Bucket data = buckets[i + k];
  449.             if(data == null)
  450.                 throw new NullPointerException();
  451.             checkBlockStatus[i] = data;
  452.         }
  453.     }
  454.  
  455.     /**
  456.      * The method used to submit {@link FECJob}s to the pool
  457.      *
  458.      * @author Florent Daigni&egrave;re &lt;[email protected]&gt;
  459.      *
  460.      * @param FECJob
  461.      */
  462.     public void addToQueue(FECJob job, FECQueue queue, ObjectContainer container) {
  463.         queue.addToQueue(job, this, container);
  464.     }
  465.    
  466.     public void objectCanDeactivate(ObjectContainer container) {
  467.         if(logMINOR) Logger.minor(this, "Deactivating "+this, new Exception("debug"));
  468.     }
  469.  
  470.     public abstract short getAlgorithm();
  471. }
Add Comment
Please, Sign In to add comment