/** * A non-authoritative hint that a specific edition *might* exist. At the moment, we just fetch * the block. We do not fetch the contents, and it is possible that USKFetcher's are also fetching * the block. FIXME would it be more efficient to pass it along to a USKFetcher? * * @param context * @throws MalformedURLException If the uri passed in is not a USK. */ public void hintUpdate(FreenetURI uri, ClientContext context, short priority) throws MalformedURLException { if (uri.getSuggestedEdition() < lookupLatestSlot(USK.create(uri))) { if (logMINOR) Logger.minor( this, "Ignoring hint because edition is " + uri.getSuggestedEdition() + " but latest is " + lookupLatestSlot(USK.create(uri))); return; } uri = uri.sskForUSK(); if (logMINOR) Logger.minor(this, "Doing hint fetch for " + uri); final ClientGetter get = new ClientGetter( new NullClientCallback(), uri, new FetchContext(backgroundFetchContext, FetchContext.IDENTICAL_MASK, false, null), priority, rcBulk, new NullBucket(), null, null); try { get.start(null, context); } catch (FetchException e) { if (logMINOR) Logger.minor(this, "Cannot start hint fetch for " + uri + " : " + e, e); // Ignore } }
void updateSlot(final USK origUSK, final long number, final ClientContext context) { if (logMINOR) Logger.minor(this, "Updating (slot) " + origUSK.getURI() + " : " + number); USK clear = origUSK.clearCopy(); final USKCallback[] callbacks; synchronized (this) { Long l = latestSlotByClearUSK.get(clear); if (logMINOR) Logger.minor(this, "Old slot: " + l); if ((l == null) || (number > l.longValue())) { l = Long.valueOf(number); latestSlotByClearUSK.put(clear, l); if (logMINOR) Logger.minor(this, "Put " + number); } else return; callbacks = subscribersByClearUSK.get(clear); if (temporaryBackgroundFetchersPrefetch.containsKey(clear)) { temporaryBackgroundFetchersPrefetch.put(clear, System.currentTimeMillis()); schedulePrefetchChecker(); } } if (callbacks != null) { // Run off-thread, because of locking, and because client callbacks may take some time final USK usk = origUSK.copy(number); for (final USKCallback callback : callbacks) context.mainExecutor.execute( new Runnable() { @Override public void run() { callback.onFoundEdition( number, usk, null, // non-persistent context, false, (short) -1, null, false, false); } }, "USKManager callback executor for " + callback); } }
void updateKnownGood(final USK origUSK, final long number, final ClientContext context) { if (logMINOR) Logger.minor(this, "Updating (known good) " + origUSK.getURI() + " : " + number); USK clear = origUSK.clearCopy(); final USKCallback[] callbacks; boolean newSlot = false; synchronized (this) { Long l = latestKnownGoodByClearUSK.get(clear); if (logMINOR) Logger.minor(this, "Old known good: " + l); if ((l == null) || (number > l.longValue())) { l = Long.valueOf(number); latestKnownGoodByClearUSK.put(clear, l); if (logMINOR) Logger.minor(this, "Put " + number); } else return; // If it's in KnownGood, it will also be in Slot l = latestSlotByClearUSK.get(clear); if (logMINOR) Logger.minor(this, "Old slot: " + l); if ((l == null) || (number > l.longValue())) { l = Long.valueOf(number); latestSlotByClearUSK.put(clear, l); if (logMINOR) Logger.minor(this, "Put " + number); newSlot = true; } callbacks = subscribersByClearUSK.get(clear); } if (callbacks != null) { // Run off-thread, because of locking, and because client callbacks may take some time final USK usk = origUSK.copy(number); final boolean newSlotToo = newSlot; for (final USKCallback callback : callbacks) context.mainExecutor.execute( new Runnable() { @Override public void run() { callback.onFoundEdition( number, usk, null, // non-persistent context, false, (short) -1, null, true, newSlotToo); } }, "USKManager callback executor for " + callback); } }
public void checkUSK( FreenetURI uri, boolean persistent, ObjectContainer container, boolean isMetadata) { try { if (persistent) container.activate(uri, 5); FreenetURI uu; if (uri.isSSK() && uri.isSSKForUSK()) { uu = uri.setMetaString(null).uskForSSK(); } else if (uri.isUSK()) { uu = uri; } else { return; } USK usk = USK.create(uu); if (!isMetadata) context.uskManager.updateKnownGood(usk, uu.getSuggestedEdition(), context); else // We don't know whether the metadata is fetchable. // FIXME add a callback so if the rest of the request completes we updateKnownGood(). context.uskManager.updateSlot(usk, uu.getSuggestedEdition(), context); } catch (MalformedURLException e) { Logger.error(this, "Caught " + e, e); } catch (Throwable t) { // Don't let the USK hint cause us to not succeed on the block. Logger.error(this, "Caught " + t, t); } }
public void onFinished(USKFetcher fetcher, boolean ignoreError) { USK orig = fetcher.getOriginalUSK(); USK clear = orig.clearCopy(); synchronized (this) { if (backgroundFetchersByClearUSK.get(clear) == fetcher) { backgroundFetchersByClearUSK.remove(clear); if (!ignoreError) { // This shouldn't happen, it's a sanity check: the only way we get cancelled is from // USKManager, which removes us before calling cancel(). Logger.error( this, "onCancelled for " + fetcher + " - was still registered, how did this happen??", new Exception("debug")); } } if (temporaryBackgroundFetchersLRU.get(clear) == fetcher) { temporaryBackgroundFetchersLRU.removeKey(clear); temporaryBackgroundFetchersPrefetch.remove(clear); } } }
/** * A non-authoritative hint that a specific edition *might* exist. At the moment, we just fetch * the block. We do not fetch the contents, and it is possible that USKFetcher's are also fetching * the block. FIXME would it be more efficient to pass it along to a USKFetcher? * * @param usk * @param edition * @param context */ public void hintUpdate(USK usk, long edition, ClientContext context) { if (edition < lookupLatestSlot(usk)) return; FreenetURI uri = usk.copy(edition).getURI().sskForUSK(); final ClientGetter get = new ClientGetter( new NullClientCallback(), uri, new FetchContext(backgroundFetchContext, FetchContext.IDENTICAL_MASK, false, null), RequestStarter.UPDATE_PRIORITY_CLASS, rcBulk, new NullBucket(), null, null); try { get.start(null, context); } catch (FetchException e) { // Ignore } }
public void unsubscribe(USK origUSK, USKCallback cb) { USKFetcher toCancel = null; synchronized (this) { USK clear = origUSK.clearCopy(); USKCallback[] callbacks = subscribersByClearUSK.get(clear); if (callbacks == null) { // maybe we should throw something ? shall we allow multiple unsubscriptions ? if (logMINOR) Logger.minor(this, "No longer subscribed"); return; } int j = 0; for (USKCallback c : callbacks) { if ((c != null) && (c != cb)) { callbacks[j++] = c; } } USKCallback[] newCallbacks = Arrays.copyOf(callbacks, j); if (newCallbacks.length > 0) subscribersByClearUSK.put(clear, newCallbacks); else { subscribersByClearUSK.remove(clear); } USKFetcher f = backgroundFetchersByClearUSK.get(clear); if (f != null) { f.removeSubscriber(cb, context); if (!f.hasSubscribers()) { toCancel = f; backgroundFetchersByClearUSK.remove(clear); } } // Temporary background fetchers run once and then die. // They do not care about callbacks. } if (toCancel != null) { toCancel.cancel(null, context); } else { if (logMINOR) Logger.minor(this, "Not found unsubscribing: " + cb + " for " + origUSK); } }
/** * Subscribe to a given USK. Callback will be notified when it is updated. Note that this does not * imply that the USK will be checked on a regular basis, unless runBackgroundFetch=true. */ public void subscribe( USK origUSK, USKCallback cb, boolean runBackgroundFetch, boolean ignoreUSKDatehints, RequestClient client) { if (logMINOR) Logger.minor(this, "Subscribing to " + origUSK + " for " + cb); if (client.persistent()) throw new UnsupportedOperationException("USKManager subscriptions cannot be persistent"); USKFetcher sched = null; long ed = origUSK.suggestedEdition; if (ed < 0) { Logger.error(this, "Subscribing to USK with negative edition number: " + ed); ed = -ed; } long curEd; curEd = lookupLatestSlot(origUSK); long goodEd; goodEd = lookupKnownGood(origUSK); synchronized (this) { USK clear = origUSK.clearCopy(); USKCallback[] callbacks = subscribersByClearUSK.get(clear); if (callbacks == null) { callbacks = new USKCallback[] {cb}; } else { boolean mustAdd = true; for (USKCallback callback : callbacks) { if (callback == cb) { // Already subscribed. // But it may still be waiting for the callback. if (!(curEd > ed || goodEd > ed)) return; mustAdd = false; } } if (mustAdd) { callbacks = Arrays.copyOf(callbacks, callbacks.length + 1); callbacks[callbacks.length - 1] = cb; } } subscribersByClearUSK.put(clear, callbacks); if (runBackgroundFetch) { USKFetcher f = backgroundFetchersByClearUSK.get(clear); if (f == null) { f = new USKFetcher( origUSK, this, ignoreUSKDatehints ? backgroundFetchContextIgnoreDBR : backgroundFetchContext, new USKFetcherWrapper(origUSK, RequestStarter.UPDATE_PRIORITY_CLASS, client), 3, true, false, false); sched = f; backgroundFetchersByClearUSK.put(clear, f); } f.addSubscriber(cb, origUSK.suggestedEdition); } } if (goodEd > ed) cb.onFoundEdition( goodEd, origUSK.copy(curEd), null, context, false, (short) -1, null, true, curEd > ed); else if (curEd > ed) cb.onFoundEdition( curEd, origUSK.copy(curEd), null, context, false, (short) -1, null, false, false); final USKFetcher fetcher = sched; if (fetcher != null) { executor.execute( new Runnable() { @Override public void run() { if (logMINOR) Logger.minor(this, "Starting " + fetcher); fetcher.schedule(null, context); } }, "USKManager.schedule for " + fetcher); } }
@Override public void run() { if (logDEBUG) Logger.debug(this, "Running prefetch checker..."); ArrayList<USK> toFetch = null; long now = System.currentTimeMillis(); boolean empty = true; synchronized (USKManager.this) { for (Map.Entry<USK, Long> entry : temporaryBackgroundFetchersPrefetch.entrySet()) { empty = false; if (entry.getValue() > 0 && now - entry.getValue() >= PREFETCH_DELAY) { if (toFetch == null) toFetch = new ArrayList<USK>(); USK clear = entry.getKey(); long l = lookupLatestSlot(clear); if (lookupKnownGood(clear) < l) toFetch.add(clear.copy(l)); entry.setValue(-1L); // Reset counter until new data comes in } else { if (logMINOR) Logger.minor( this, "Not prefetching: " + entry.getKey() + " : " + entry.getValue()); } } } if (toFetch == null) return; for (final USK key : toFetch) { final long l = key.suggestedEdition; if (logMINOR) Logger.minor( this, "Prefetching content for background fetch for edition " + l + " on " + key); FetchContext fctx = new FetchContext(realFetchContext, FetchContext.IDENTICAL_MASK, false, null); final ClientGetter get = new ClientGetter( new ClientGetCallback() { @Override public void onFailure( FetchException e, ClientGetter state, ObjectContainer container) { if (e.newURI != null) { if (logMINOR) Logger.minor(this, "Prefetch succeeded with redirect for " + key); updateKnownGood(key, l, context); return; } else { if (logMINOR) Logger.minor(this, "Prefetch failed later: " + e + " for " + key, e); // Ignore } } @Override public void onSuccess( FetchResult result, ClientGetter state, ObjectContainer container) { if (logMINOR) Logger.minor(this, "Prefetch succeeded for " + key); result.asBucket().free(); updateKnownGood(key, l, context); } @Override public void onMajorProgress(ObjectContainer container) { // Ignore } }, key.getURI().sskForUSK() /* FIXME add getSSKURI() */, fctx, RequestStarter.UPDATE_PRIORITY_CLASS, rcBulk, new NullBucket(), null, null); try { get.start(null, context); } catch (FetchException e) { if (logMINOR) Logger.minor(this, "Prefetch failed: " + e, e); // Ignore } } if (!empty) schedulePrefetchChecker(); }
public void startTemporaryBackgroundFetcher( USK usk, ClientContext context, final FetchContext fctx, boolean prefetchContent, boolean realTimeFlag) { final USK clear = usk.clearCopy(); USKFetcher sched = null; ArrayList<USKFetcher> toCancel = null; synchronized (this) { // int x = 0; // for(USK key: backgroundFetchersByClearUSK.keySet()) { // System.err.println("Fetcher "+x+": "+key); // x++; // } USKFetcher f = temporaryBackgroundFetchersLRU.get(clear); if (f == null) { f = new USKFetcher( usk, this, fctx.ignoreUSKDatehints ? backgroundFetchContextIgnoreDBR : backgroundFetchContext, new USKFetcherWrapper( usk, RequestStarter.UPDATE_PRIORITY_CLASS, realTimeFlag ? rcRT : rcBulk), 3, false, false, false); sched = f; temporaryBackgroundFetchersLRU.push(clear, f); } else { f.addHintEdition(usk.suggestedEdition); } if (prefetchContent) { long fetchTime = -1; // If nothing in 60 seconds, try fetching the last known slot. long slot = lookupLatestSlot(clear); long good = lookupKnownGood(clear); if (slot > -1 && good != slot) fetchTime = System.currentTimeMillis(); temporaryBackgroundFetchersPrefetch.put(clear, fetchTime); if (logMINOR) Logger.minor(this, "Prefetch: set " + fetchTime + " for " + clear); schedulePrefetchChecker(); } temporaryBackgroundFetchersLRU.push(clear, f); while (temporaryBackgroundFetchersLRU.size() > NodeClientCore.getMaxBackgroundUSKFetchers()) { USKFetcher fetcher = temporaryBackgroundFetchersLRU.popValue(); temporaryBackgroundFetchersPrefetch.remove(fetcher.getOriginalUSK().clearCopy()); if (!fetcher.hasSubscribers()) { if (toCancel == null) toCancel = new ArrayList<USKFetcher>(2); toCancel.add(fetcher); } else { if (logMINOR) Logger.minor( this, "Allowing temporary background fetcher to continue as it has subscribers... " + fetcher); } } } final ArrayList<USKFetcher> cancelled = toCancel; final USKFetcher scheduleMe = sched; // This is just a prefetching method. so it should not unnecessarily delay the parent, nor // should it take important locks. // So we should do the actual schedule/cancels off-thread. // However, the above is done on-thread because a lot of the time it will already be running. if (cancelled != null || sched != null) { executor.execute( new Runnable() { @Override public void run() { if (cancelled != null) { for (int i = 0; i < cancelled.size(); i++) { USKFetcher fetcher = cancelled.get(i); fetcher.cancel(null, USKManager.this.context); } } if (scheduleMe != null) scheduleMe.schedule(null, USKManager.this.context); } }); } }
/** * Look up the latest SSK slot, whether the data it links to has been successfully fetched or not, * of the given USK. * * @return The latest known edition number, or -1. */ public synchronized long lookupLatestSlot(USK usk) { Long l = latestSlotByClearUSK.get(usk.clearCopy()); if (l != null) return l.longValue(); else return -1; }