package jelectrum; import java.util.Map; import java.util.HashSet; import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; /** * After solving something using a complex solution: * - A novice would say, look at how clever of a soltuion I made * - A jounryman would say, it was complicated but it is done * - A master would say, I have failed to make it simple * * So this is complicated and I have failed to make it simple. * The idea here is that as we import transaction, many addresses * will need multiple updates to the transaction lists for them at * the same time. But we can't let them go to the database as the * changes will overwrite one another. So we need to serialize them. * But that is very slow, so we need to group them into batches that * can happen at all once. And we need the calling thread to wait until * the batch is done because they need to know it was written. * */ public class BandedUpdater<K, V> { Map<K, HashSet<V> > base_map; Map<K, UpdateGroup> pending_updates; HashSet<K> in_progress; LinkedBlockingQueue<K> to_check; public BandedUpdater(Map<K, HashSet<V> > map, int threads) { base_map = map; pending_updates = new HashMap<K, UpdateGroup>(256,0.5f); in_progress = new HashSet<K>(256,0.5f); to_check = new LinkedBlockingQueue<K>(); for(int i=0; i<threads; i++) { new BandedUpdaterThread().start(); } } /** * Adds an item to the hashset associated with the key. * Blocks until the write is complete. */ public int addItem(K key, V item) { UpdateGroup ug = null; synchronized(pending_updates) { if (pending_updates.containsKey(key)) { ug = pending_updates.get(key); } else { ug = new UpdateGroup(); pending_updates.put(key, ug); } ug.items.add(item); } try { to_check.put(key); ug.await(); } catch(java.lang.InterruptedException e) { throw new RuntimeException(e); } return ug.new_size; } public class BandedUpdaterThread extends Thread { public BandedUpdaterThread() { setName("BandedUpdaterThread"); setDaemon(true); } public void run() { while(true) { K key = null; try { key = to_check.take(); tryKey(key); } catch(Throwable e) { e.printStackTrace(); if (key!=null) { try { to_check.put(key); } catch(java.lang.InterruptedException e2) { throw new RuntimeException(e2); } } } } } private void tryKey(K key) throws java.lang.InterruptedException { UpdateGroup ug = null; synchronized(in_progress) { if (in_progress.contains(key)) { return; } synchronized(pending_updates) { ug = pending_updates.get(key); if (ug == null) return; pending_updates.remove(key); in_progress.add(key); } } try { //We have an update group all to ourself //and have marked in progress //rock it. HashSet<V> old = base_map.get(key); HashSet<V> new_set = new HashSet<V>(); if (old!=null) { new_set.addAll(old); } new_set.addAll(ug.items); base_map.put(key, new_set); ug.latch.countDown(); ug.new_size = new_set.size(); if (ug.items.size() > 10) { //jelly.getEventLog().log("Banded " + ug.items.size() + " for key " + key); } } finally { synchronized(in_progress) { in_progress.remove(key); } to_check.put(key); } } } public class UpdateGroup { HashSet<V> items=new HashSet<V>(); CountDownLatch latch=new CountDownLatch(1); int new_size; public void await() { try { latch.await(); } catch(java.lang.InterruptedException e) { throw new RuntimeException(e); } } } }