[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[GNUnet-SVN] r21370 - in gnunet-java: . src/org/gnunet/statistics src/or
From: |
gnunet |
Subject: |
[GNUnet-SVN] r21370 - in gnunet-java: . src/org/gnunet/statistics src/org/gnunet/util src/org/grothoff test/org test/org/grothoff |
Date: |
Wed, 9 May 2012 12:07:53 +0200 |
Author: dold
Date: 2012-05-09 12:07:53 +0200 (Wed, 09 May 2012)
New Revision: 21370
Added:
gnunet-java/src/org/gnunet/statistics/GetMessage.java
gnunet-java/src/org/gnunet/statistics/GetResponseEndMessage.java
gnunet-java/src/org/gnunet/statistics/GetResponseMessage.java
gnunet-java/src/org/gnunet/statistics/WatchMessage.java
gnunet-java/src/org/gnunet/statistics/WatchResponseMessage.java
gnunet-java/src/org/gnunet/util/RequestQueue.java
gnunet-java/test/org/grothoff/
gnunet-java/test/org/grothoff/RunaboutBenchmark.java
Removed:
gnunet-java/src/org/gnunet/statistics/Request.java
gnunet-java/src/org/gnunet/statistics/RequestMessage.java
gnunet-java/src/org/gnunet/statistics/RequestQueue.java
gnunet-java/src/org/gnunet/statistics/ResponseEndMessage.java
gnunet-java/src/org/gnunet/statistics/ResponseValueMessage.java
Modified:
gnunet-java/ISSUES
gnunet-java/src/org/gnunet/statistics/SetMessage.java
gnunet-java/src/org/gnunet/statistics/Statistics.java
gnunet-java/src/org/gnunet/util/AbsoluteTime.java
gnunet-java/src/org/gnunet/util/Connection.java
gnunet-java/src/org/gnunet/util/Scheduler.java
gnunet-java/src/org/grothoff/Runabout.java
Log:
implemented the RequestQueue, which is now used in statistics. statistics is
now more robust and supports watching values. implemented support for anonymous
inner classes in the runabout.
Modified: gnunet-java/ISSUES
===================================================================
--- gnunet-java/ISSUES 2012-05-09 09:59:06 UTC (rev 21369)
+++ gnunet-java/ISSUES 2012-05-09 10:07:53 UTC (rev 21370)
@@ -3,4 +3,20 @@
* gnunet vs GNUnet, gnunet-java project name
* I'm currently trying to increase the robustness of the service APIs,
discuss behavior in some cases
-* packaging requirements
\ No newline at end of file
+* packaging requirements
+
+
+
+* the Runabout can now be an anonymous inner class
+ * implementation overhead: for public subclasses only constant overhead in
the Runabout constructor
+ * for private/anonymous inner classes: 10-20% overhead, measured over 100M
calls
+ * only issue left: visit methods have to be public, but this is a non issue.
+
+
+* Statistics:
+ * currently watches can't be canceled on the service level, only on the api
level, is this intentional?
+
+* with the changes in ARM there is no way to restart gnunet with arm if some
client is misbehaving
+
+* I'm often getting
+ May 09 09:21:49-194786 util-11121 WARNING `socket' failed at
connection.c:892 with error: Too many open files
Copied: gnunet-java/src/org/gnunet/statistics/GetMessage.java (from rev 21245,
gnunet-java/src/org/gnunet/statistics/RequestMessage.java)
===================================================================
--- gnunet-java/src/org/gnunet/statistics/GetMessage.java
(rev 0)
+++ gnunet-java/src/org/gnunet/statistics/GetMessage.java 2012-05-09
10:07:53 UTC (rev 21370)
@@ -0,0 +1,23 @@
+package org.gnunet.statistics;
+
+import org.gnunet.construct.UnionCase;
+import org.gnunet.construct.ZeroTerminatedString;
+import org.gnunet.util.GnunetMessage;
+
+/**
+ * Client --> Service
+ *
+ */
address@hidden(169)
+public class GetMessage implements GnunetMessage.Body {
+ /**
+ * Subsystem of interest, empty string for all subsystems.
+ */
+ @ZeroTerminatedString
+ public String subsystemName;
+ /**
+ * Statistics value name of interest, empty string for all values.
+ */
+ @ZeroTerminatedString
+ public String statisticsName;
+}
Copied: gnunet-java/src/org/gnunet/statistics/GetResponseEndMessage.java (from
rev 21245, gnunet-java/src/org/gnunet/statistics/ResponseEndMessage.java)
===================================================================
--- gnunet-java/src/org/gnunet/statistics/GetResponseEndMessage.java
(rev 0)
+++ gnunet-java/src/org/gnunet/statistics/GetResponseEndMessage.java
2012-05-09 10:07:53 UTC (rev 21370)
@@ -0,0 +1,16 @@
+package org.gnunet.statistics;
+
+import org.gnunet.construct.UnionCase;
+import org.gnunet.util.GnunetMessage;
+
+/**
+* Created with IntelliJ IDEA.
+* User: dold
+* Date: 5/2/12
+* Time: 7:09 PM
+* To change this template use File | Settings | File Templates.
+*/
address@hidden(171)
+public class GetResponseEndMessage implements GnunetMessage.Body {
+ // empty
+}
Copied: gnunet-java/src/org/gnunet/statistics/GetResponseMessage.java (from rev
21245, gnunet-java/src/org/gnunet/statistics/ResponseValueMessage.java)
===================================================================
--- gnunet-java/src/org/gnunet/statistics/GetResponseMessage.java
(rev 0)
+++ gnunet-java/src/org/gnunet/statistics/GetResponseMessage.java
2012-05-09 10:07:53 UTC (rev 21370)
@@ -0,0 +1,29 @@
+package org.gnunet.statistics;
+
+import org.gnunet.construct.UInt32;
+import org.gnunet.construct.UInt64;
+import org.gnunet.construct.UnionCase;
+import org.gnunet.construct.ZeroTerminatedString;
+import org.gnunet.util.GnunetMessage;
+
+/**
+ * service --> client
+ *
+ *
+ */
address@hidden(170)
+public class GetResponseMessage implements GnunetMessage.Body {
+ /**
+ * Unique numerical identifier for the value (will
+ * not change during the same client-session). Highest
+ * bit will be set for persistent values.
+ */
+ @UInt32
+ public long uid;
+ @UInt64
+ public long value;
+ @ZeroTerminatedString
+ public String subsystemName;
+ @ZeroTerminatedString
+ public String statisticName;
+}
Deleted: gnunet-java/src/org/gnunet/statistics/Request.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/Request.java 2012-05-09 09:59:06 UTC
(rev 21369)
+++ gnunet-java/src/org/gnunet/statistics/Request.java 2012-05-09 10:07:53 UTC
(rev 21370)
@@ -1,29 +0,0 @@
-package org.gnunet.statistics;
-
-import org.gnunet.util.AbsoluteTime;
-import org.gnunet.util.MessageTransmitter;
-
-public interface Request extends MessageTransmitter {
- /**
- * cancel action specific to a certain type of request
- */
- void cancel();
-
- /**
- * Deadline for transmitting the request.
- *
- */
- AbsoluteTime getDeadline();
-
- /**
- *
- * @return true if the request should be kept after the destroy request
- */
- boolean onDestroy();
-
- /**
- *
- * @return true if the request should be kept after the reconnect
- */
- boolean onReconnect();
-}
Deleted: gnunet-java/src/org/gnunet/statistics/RequestMessage.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/RequestMessage.java 2012-05-09
09:59:06 UTC (rev 21369)
+++ gnunet-java/src/org/gnunet/statistics/RequestMessage.java 2012-05-09
10:07:53 UTC (rev 21370)
@@ -1,13 +0,0 @@
-package org.gnunet.statistics;
-
-import org.gnunet.construct.UnionCase;
-import org.gnunet.construct.ZeroTerminatedString;
-import org.gnunet.util.GnunetMessage;
-
address@hidden(169)
-public class RequestMessage implements GnunetMessage.Body {
- @ZeroTerminatedString
- public String subsystemName;
- @ZeroTerminatedString
- public String statisticsName;
-}
Deleted: gnunet-java/src/org/gnunet/statistics/RequestQueue.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/RequestQueue.java 2012-05-09
09:59:06 UTC (rev 21369)
+++ gnunet-java/src/org/gnunet/statistics/RequestQueue.java 2012-05-09
10:07:53 UTC (rev 21370)
@@ -1,129 +0,0 @@
-package org.gnunet.statistics;
-
-import org.gnunet.util.*;
-
-import java.util.LinkedList;
-
-/**
-* Created with IntelliJ IDEA.
-* User: dold
-* Date: 5/2/12
-* Time: 8:03 PM
-* To change this template use File | Settings | File Templates.
-*/
-public class RequestQueue {
-
- /**
- * Requests to be transmitted to the service.
- */
- private final LinkedList<Request> requests = new LinkedList<Request>();
- /**
- * The designated receiver for all messages.
- */
- private MessageReceiver receiver;
-
- /**
- * The active transmit request handle, if any.
- */
- private Cancelable currentTransmit;
-
- /**
- * Current receive handler.
- */
- private Cancelable currentReceive;
-
- private boolean destroyRequested = false;
- private final Client client;
-
- public RequestQueue(Client client, MessageReceiver receiver) {
- this.client = client;
- this.receiver = receiver;
- }
-
- /**
- * Handle next request.
- */
- private void handleNextRequest() {
- if (currentTransmit != null) {
- return;
- }
-
- final Request request = requests.poll();
- if (request == null) {
- if (destroyRequested) {
- client.disconnect();
- }
- return;
- }
-
- currentTransmit =
client.notifyTransmitReady(request.getDeadline().getRemaining(), true, 0, new
MessageTransmitter() {
- @Override
- public void transmit(Connection.MessageSink sink) {
- currentTransmit = null;
-
- request.transmit(sink);
-
- handleReceive();
- handleNextRequest();
- }
-
- @Override
- public void handleError() {
- request.handleError();
- }
- });
- }
-
- private void handleReceive() {
- if (currentReceive != null || destroyRequested) {
- return;
- }
- currentReceive = client.receive(RelativeTime.FOREVER, new
MessageReceiver() {
- @Override
- public void process(GnunetMessage.Body msg) {
- currentReceive = null;
-
- receiver.process(msg);
-
- handleNextRequest();
- handleReceive();
- }
-
- @Override
- public void handleError() {
- receiver.handleError();
- }
- });
- }
-
- public Cancelable add(final Request request) {
- requests.add(request);
- handleNextRequest();
-
- return new Cancelable() {
- @Override
- public void cancel() {
- RequestQueue.this.requests.remove(request);
- request.cancel();
- }
- };
- }
-
- public void destroy() {
- destroyRequested = true;
-
- final LinkedList<Request> remove = new LinkedList<Request>();
-
- for (Request r : requests) {
- boolean keep = r.onDestroy();
- if (!keep) {
- remove.add(r);
- }
- }
- requests.removeAll(remove);
-
- if (requests.isEmpty()) {
- client.disconnect();
- }
- }
-}
Deleted: gnunet-java/src/org/gnunet/statistics/ResponseEndMessage.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/ResponseEndMessage.java
2012-05-09 09:59:06 UTC (rev 21369)
+++ gnunet-java/src/org/gnunet/statistics/ResponseEndMessage.java
2012-05-09 10:07:53 UTC (rev 21370)
@@ -1,16 +0,0 @@
-package org.gnunet.statistics;
-
-import org.gnunet.construct.UnionCase;
-import org.gnunet.util.GnunetMessage;
-
-/**
-* Created with IntelliJ IDEA.
-* User: dold
-* Date: 5/2/12
-* Time: 7:09 PM
-* To change this template use File | Settings | File Templates.
-*/
address@hidden(171)
-public class ResponseEndMessage implements GnunetMessage.Body {
- // empty
-}
Deleted: gnunet-java/src/org/gnunet/statistics/ResponseValueMessage.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/ResponseValueMessage.java
2012-05-09 09:59:06 UTC (rev 21369)
+++ gnunet-java/src/org/gnunet/statistics/ResponseValueMessage.java
2012-05-09 10:07:53 UTC (rev 21370)
@@ -1,31 +0,0 @@
-package org.gnunet.statistics;
-
-import org.gnunet.construct.UInt32;
-import org.gnunet.construct.UInt64;
-import org.gnunet.construct.UnionCase;
-import org.gnunet.construct.ZeroTerminatedString;
-import org.gnunet.util.GnunetMessage;
-
-/**
-* Created with IntelliJ IDEA.
-* User: dold
-* Date: 5/2/12
-* Time: 7:09 PM
-* To change this template use File | Settings | File Templates.
-*/
address@hidden(170)
-public class ResponseValueMessage implements GnunetMessage.Body {
- /**
- * Unique numerical identifier for the value (will
- * not change during the same client-session). Highest
- * bit will be set for persistent values.
- */
- @UInt32
- public long uid;
- @UInt64
- public long value;
- @ZeroTerminatedString
- public String subsystemName;
- @ZeroTerminatedString
- public String statisticName;
-}
Modified: gnunet-java/src/org/gnunet/statistics/SetMessage.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/SetMessage.java 2012-05-09
09:59:06 UTC (rev 21369)
+++ gnunet-java/src/org/gnunet/statistics/SetMessage.java 2012-05-09
10:07:53 UTC (rev 21370)
@@ -6,13 +6,10 @@
import org.gnunet.construct.ZeroTerminatedString;
import org.gnunet.util.GnunetMessage;
+
/**
-* Created with IntelliJ IDEA.
-* User: dold
-* Date: 5/2/12
-* Time: 7:10 PM
-* To change this template use File | Settings | File Templates.
-*/
+ * Sent to the service by the client to set a statistics value.
+ */
@UnionCase(168)
public class SetMessage implements GnunetMessage.Body {
@UInt32
Modified: gnunet-java/src/org/gnunet/statistics/Statistics.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/Statistics.java 2012-05-09
09:59:06 UTC (rev 21369)
+++ gnunet-java/src/org/gnunet/statistics/Statistics.java 2012-05-09
10:07:53 UTC (rev 21370)
@@ -6,12 +6,15 @@
package org.gnunet.statistics;
+import org.gnunet.construct.UnionCase;
import org.gnunet.util.*;
import org.gnunet.util.getopt.Option;
import org.gnunet.util.getopt.OptionAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+
/**
* API for the gnunet statistics service.
* <p/>
@@ -28,21 +31,36 @@
*/
private static final RelativeTime SET_TIMEOUT =
RelativeTime.SECOND.multiply(5);
- private final static int SETFLAG_ABSOLUTE = 0;
private final static int SETFLAG_RELATIVE = 1;
private final static int SETFLAG_PERSIST = 2;
- private Client client;
+ private final Client client;
- private RequestQueue requestQueue;
+ private final RequestQueue requestQueue;
+ /**
+ * Callback for the current get request. Only one get request ist allowed
at a time.
+ */
private StatisticsReceiver currentGetReceiver;
+ /**
+ * Success/Error continuation for the current get request.
+ */
private Continuation currentGetContinuation;
+ ArrayList<StatisticsWatchRequest> watchRequests = new
ArrayList<StatisticsWatchRequest>();
+
+ public interface StatisticsReceiver {
+ public void onReceive(String subsystem, String name, long value);
+ }
+
+ public interface Continuation {
+ public void cont(boolean success);
+ }
+
/**
* A request to the statistics service.
*/
- private abstract class StatisticsRequest implements Request {
+ private abstract class StatisticsRequest extends RequestQueue.Request {
public String name;
public String subsystem;
public AbsoluteTime deadline;
@@ -50,53 +68,37 @@
private class StatisticsGetRequest extends StatisticsRequest {
public StatisticsReceiver receiver;
- public Cancelable receiveHandle;
- @Override
- public void cancel() {
+ public void onCancel(boolean alreadyTransmitted) {
currentGetReceiver = null;
}
- @Override
+ public AbsoluteTime getDeadline() {
+ return deadline;
+ }
+
+
public void transmit(Connection.MessageSink sink) {
- RequestMessage rm = new RequestMessage();
+ GetMessage rm = new GetMessage();
rm.statisticsName = name;
rm.subsystemName = subsystem;
sink.send(rm);
}
- @Override
- public void handleError() {
- throw new RuntimeException("unexpected transmit error");
- }
-
- @Override
- public AbsoluteTime getDeadline() {
- return deadline;
- }
-
- @Override
- public boolean onDestroy() {
- return false;
- }
-
- @Override
public boolean onReconnect() {
return true;
}
}
- private class StatisticsPutRequest extends StatisticsRequest implements
Cancelable {
+ private class StatisticsSetRequest extends StatisticsRequest {
public long value;
public int flags;
- @Override
- public void cancel() {
- // do nothing
+ public AbsoluteTime getDeadline() {
+ return SET_TIMEOUT.toAbsolute();
}
- @Override
public void transmit(Connection.MessageSink sink) {
SetMessage sm = new SetMessage();
sm.statisticName = name;
@@ -106,52 +108,91 @@
sink.send(sm);
}
- @Override
- public void handleError() {
- throw new RuntimeException("unexpected transmit error");
+ public boolean onDestroy() {
+ // keep the request
+ return true;
}
- @Override
+ public boolean onReconnect() {
+ // just keep the request on reconnect
+ return true;
+ }
+ }
+
+
+ private class StatisticsWatchRequest extends StatisticsRequest {
+ public StatisticsReceiver receiver;
+
public AbsoluteTime getDeadline() {
- return deadline;
+ return AbsoluteTime.FOREVER;
}
- @Override
- public boolean onDestroy() {
- return true;
+ public void onCancel(boolean alreadyTransmitted) {
+ System.out.println("already transmitted: " + alreadyTransmitted);
+ if (alreadyTransmitted) {
+ watchRequests.set(watchRequests.indexOf(this), null);
+ }
+
}
- @Override
public boolean onReconnect() {
+ System.out.println("onReconnect()!");
+
+ // do this because we'll probably get new WatchIDs for every watch
request on reconnect
+ if (watchRequests.contains(this)) {
+ watchRequests.clear();
+ }
+
+ watchRequests.add(this);
+
return true;
}
+
+ public void transmit(Connection.MessageSink sink) {
+ WatchMessage wm = new WatchMessage();
+ wm.statisticsName = name;
+ wm.subsystemName = subsystem;
+ sink.send(wm);
+
+
+ watchRequests.add(this);
+ }
}
public class StatisticsMessageReceiver extends RunaboutMessageReceiver {
- public void visit(ResponseValueMessage m) {
+ public void visit(GetResponseMessage m) {
currentGetReceiver.onReceive(m.subsystemName, m.statisticName,
m.value);
}
- public void visit(ResponseEndMessage m) {
+ public void visit(GetResponseEndMessage m) {
if (currentGetContinuation != null) {
- currentGetContinuation.onDone();
+ currentGetContinuation.cont(true);
}
}
+ public void visit(TESTMessage m) {
+ client.disconnect();
+ }
+
+ public void visit(WatchResponseMessage wrm) {
+ if (watchRequests.size() <= wrm.wid) {
+ logger.warn("statistics service got confused with watch
request");
+ return;
+ }
+ StatisticsWatchRequest wr = watchRequests.get(wrm.wid);
+ // request may have been canceled by the api (but not by the
server)
+ if (wr != null) {
+ wr.receiver.onReceive(wr.subsystem, wr.name, wrm.value);
+ }
+ }
+
@Override
public void handleError() {
+ requestQueue.reconnect();
}
}
- public interface StatisticsReceiver {
- public void onReceive(String subsystem, String name, long value);
- }
-
- public interface Continuation {
- public void onDone();
- }
-
public Statistics(Configuration cfg) {
client = new Client("statistics", cfg);
requestQueue = new RequestQueue(this.client, new
StatisticsMessageReceiver());
@@ -160,11 +201,10 @@
/**
* Retrieve values from statistics.
*
- *
- * @param timeout time after we give up and call receiver.onTimeout
- * @param subsystem the subsystem of interest
- * @param name name of the statistics value belongs to
- * @param receiver callback
+ * @param timeout time after we give up and call receiver.onTimeout
+ * @param subsystem the subsystem of interest
+ * @param name name of the statistics value belongs to
+ * @param receiver callback
* @param continuation
* @return handle to cancel the request
*/
@@ -179,8 +219,8 @@
final StatisticsGetRequest getRequest = new StatisticsGetRequest();
getRequest.deadline = timeout.toAbsolute();
- getRequest.name = name == null ? "" : name;
- getRequest.subsystem = subsystem == null ? "" : name;
+ getRequest.name = (name == null) ? "" : name;
+ getRequest.subsystem = (subsystem == null) ? "" : subsystem;
getRequest.receiver = receiver;
return requestQueue.add(getRequest);
@@ -196,30 +236,59 @@
* @return a handle to cancel the request
*/
public Cancelable set(final String subsystem, final String name, final
long value, boolean persist) {
- StatisticsPutRequest putRequest = new StatisticsPutRequest();
- putRequest.deadline = SET_TIMEOUT.toAbsolute();
- putRequest.subsystem = subsystem;
- putRequest.name = name;
- putRequest.value = value;
- putRequest.flags = persist ? SETFLAG_PERSIST : 0;
+ StatisticsSetRequest setRequest = new StatisticsSetRequest();
+ setRequest.deadline = SET_TIMEOUT.toAbsolute();
+ setRequest.subsystem = subsystem;
+ setRequest.name = name;
+ setRequest.value = value;
+ setRequest.flags = persist ? SETFLAG_PERSIST : 0;
- return requestQueue.add(putRequest);
+ return requestQueue.add(setRequest);
}
+ /**
+ * Changes a statistics value asynchronously.
+ *
+ * @param name name of the entry
+ * @param delta relative difference to the old value
+ * @param persist keep value even if the statistics service restarts
+ * @return a handle to cancel the request
+ */
+ public Cancelable update(final String subsystem, final String name, final
long delta, boolean persist) {
+ StatisticsSetRequest setRequest = new StatisticsSetRequest();
+ setRequest.deadline = SET_TIMEOUT.toAbsolute();
+ setRequest.subsystem = subsystem;
+ setRequest.name = name;
+ setRequest.value = delta;
+ setRequest.flags = (persist ? SETFLAG_PERSIST : 0) | SETFLAG_RELATIVE;
+
+ return requestQueue.add(setRequest);
+ }
+
public Cancelable watch(final String subsystem, final String name,
StatisticsReceiver receiver) {
- return null;
+ StatisticsWatchRequest watchRequest = new StatisticsWatchRequest();
+ watchRequest.deadline = AbsoluteTime.FOREVER;
+ watchRequest.name = name;
+ watchRequest.subsystem = subsystem;
+ watchRequest.receiver = receiver;
+
+ // even after the request has been sent, we want to keep it
+ // (e.g. for retransmission on reconnect)
+ return requestQueue.addPersistent(watchRequest);
}
/**
* Destroy handle to the statistics service. Always finishes writing
pending values.
*/
public void destroy() {
+ // the request queue handles the destruction, maybe we still have
important messages pending etc.
+ requestQueue.add(new TESTRequest());
requestQueue.destroy();
}
/**
- * Statistics utility entry point
+ * Statistics command line utility entry point
*
* @param args command line arguments
*/
@@ -232,6 +301,12 @@
description = "set a value")
boolean test;
@Option(
+ shortname = "w",
+ longname = "watch",
+ action = OptionAction.SET,
+ description = "set a value")
+ boolean watch;
+ @Option(
shortname = "n",
longname = "name",
action = OptionAction.STORE_STRING,
@@ -266,19 +341,31 @@
statistics.destroy();
} else {
if (unprocessedArgs.length == 0) {
- statistics.get(RelativeTime.SECOND, subsystemName,
statisticsName,
- new StatisticsReceiver() {
- @Override
- public void onReceive(String subsystem,
String name, long value) {
- System.out.println(subsystem + "(" +
name + ") = " + value);
+ if (watch) {
+ final Cancelable c =
statistics.watch(subsystemName, statisticsName,
+ new StatisticsReceiver() {
+ @Override
+ public void onReceive(String
subsystem, String name, long value) {
+ System.out.println(subsystem + "("
+ name + ") = " + value);
+ }
}
- },
- new Continuation() {
- @Override
- public void onDone() {
- statistics.destroy();
+ );
+ } else {
+ statistics.get(RelativeTime.SECOND, subsystemName,
statisticsName,
+ new StatisticsReceiver() {
+ @Override
+ public void onReceive(String
subsystem, String name, long value) {
+ System.out.println(subsystem + "("
+ name + ") = " + value);
+ }
+ },
+ new Continuation() {
+ @Override
+ public void cont(boolean success) {
+ statistics.destroy();
+ }
}
- });
+ );
+ }
} else {
System.err.println("dumping statistics does not take
any positional parameters");
}
@@ -286,4 +373,26 @@
}
}.start();
}
+
+
+ @UnionCase(1)
+ public static class TESTMessage implements GnunetMessage.Body {
+ // empty
+ }
+
+ private class TESTRequest extends RequestQueue.Request {
+ public boolean onDestroy() {
+ // keep on destroy
+ return true;
+ }
+
+ public AbsoluteTime getDeadline() {
+ return SET_TIMEOUT.toAbsolute();
+ }
+
+ public void transmit(Connection.MessageSink sink) {
+ sink.send(new TESTMessage());
+ // todo: disconnect when not receiving the TEST message back after
timeout
+ }
+ }
}
Added: gnunet-java/src/org/gnunet/statistics/WatchMessage.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/WatchMessage.java
(rev 0)
+++ gnunet-java/src/org/gnunet/statistics/WatchMessage.java 2012-05-09
10:07:53 UTC (rev 21370)
@@ -0,0 +1,20 @@
+package org.gnunet.statistics;
+
+
+import org.gnunet.construct.UnionCase;
+import org.gnunet.construct.ZeroTerminatedString;
+import org.gnunet.util.GnunetMessage;
+
address@hidden(172)
+public class WatchMessage implements GnunetMessage.Body {
+ /**
+ * Subsystem of interest, may not be empty.
+ */
+ @ZeroTerminatedString
+ public String subsystemName;
+ /**
+ * Statistics value name of interest, may not be empty.
+ */
+ @ZeroTerminatedString
+ public String statisticsName;
+}
Added: gnunet-java/src/org/gnunet/statistics/WatchResponseMessage.java
===================================================================
--- gnunet-java/src/org/gnunet/statistics/WatchResponseMessage.java
(rev 0)
+++ gnunet-java/src/org/gnunet/statistics/WatchResponseMessage.java
2012-05-09 10:07:53 UTC (rev 21370)
@@ -0,0 +1,19 @@
+package org.gnunet.statistics;
+
+
+import org.gnunet.construct.UInt32;
+import org.gnunet.construct.UInt64;
+import org.gnunet.construct.UnionCase;
+import org.gnunet.util.GnunetMessage;
+
address@hidden(173)
+public class WatchResponseMessage implements GnunetMessage.Body {
+ @UInt32
+ public int flags;
+ @UInt32
+ public int wid;
+ @UInt32
+ public int reserved ;
+ @UInt64
+ public long value;
+}
Modified: gnunet-java/src/org/gnunet/util/AbsoluteTime.java
===================================================================
--- gnunet-java/src/org/gnunet/util/AbsoluteTime.java 2012-05-09 09:59:06 UTC
(rev 21369)
+++ gnunet-java/src/org/gnunet/util/AbsoluteTime.java 2012-05-09 10:07:53 UTC
(rev 21370)
@@ -62,7 +62,7 @@
return AbsoluteTime.FOREVER;
}
if (abs_value + duration.getMilliseconds() < abs_value) {
- logger.warn("time overflow");
+ Thread.dumpStack();
return AbsoluteTime.FOREVER;
}
return new AbsoluteTime(abs_value + duration.getMilliseconds());
@@ -177,6 +177,9 @@
* @return this - now
*/
public RelativeTime getRemaining() {
+ if (abs_value == Long.MAX_VALUE) {
+ return RelativeTime.FOREVER;
+ }
return getDifference(AbsoluteTime.now());
}
Modified: gnunet-java/src/org/gnunet/util/Connection.java
===================================================================
--- gnunet-java/src/org/gnunet/util/Connection.java 2012-05-09 09:59:06 UTC
(rev 21369)
+++ gnunet-java/src/org/gnunet/util/Connection.java 2012-05-09 10:07:53 UTC
(rev 21370)
@@ -73,6 +73,7 @@
* transmitting larger messages.
*/
private ByteBuffer transmitBuffer =
ByteBuffer.allocate(GnunetMessage.Header.SIZE);
+ private boolean disconnected=false;
private class AddressProbe {
@@ -148,6 +149,8 @@
receiver.handleError();
return;
}
+ // todo: this was most probably wrong, revise it!
+ /*
if (n == 0) {
logger.info("service gracefully disconnected us");
connectionChannel.close();
@@ -155,6 +158,7 @@
receiver.handleError();
return;
}
+ */
logger.debug(String.format("connectionChannel read %s
bytes", n));
} catch (IOException e) {
logger.error("read failed with exception:", e);
@@ -502,6 +506,11 @@
* Disconnect. Cancel pending receive/transmit requests.
*/
public void disconnect() {
+ if (disconnected) {
+ logger.warn("disconnect called twice");
+ }
+ disconnected = true;
+
logger.debug(""+this+".disconnect()");
if (nextTransmitHelper != null) {
nextTransmitHelper.cancel();
Copied: gnunet-java/src/org/gnunet/util/RequestQueue.java (from rev 21245,
gnunet-java/src/org/gnunet/statistics/RequestQueue.java)
===================================================================
--- gnunet-java/src/org/gnunet/util/RequestQueue.java
(rev 0)
+++ gnunet-java/src/org/gnunet/util/RequestQueue.java 2012-05-09 10:07:53 UTC
(rev 21370)
@@ -0,0 +1,218 @@
+package org.gnunet.util;
+
+import java.util.LinkedList;
+
+/**
+ * Generic queues for Requests to be sent to the service.
+ */
+public class RequestQueue {
+
+ /**
+ * Requests to be transmitted to the service.
+ */
+ private final LinkedList<Request> requestsAwaitingTransmit = new
LinkedList<Request>();
+
+
+ /**
+ * Persistent requests. Will be informed about reconnect / destroy events
even
+ * if already transmitted. Have to be canceled manually.
+ */
+ private final LinkedList<Request> persistentRequests = new
LinkedList<Request>();
+
+
+ /**
+ * List of all requests from requestAwaitingTransmit and
persistentRequest, containing no duplicates.
+ */
+ private final LinkedList<Request> allRequests = new LinkedList<Request>();
+
+ /**
+ * The designated receiver for all messages.
+ */
+ private MessageReceiver receiver;
+
+ /**
+ * The active transmit request handle, if any.
+ */
+ private Cancelable currentTransmit;
+
+ /**
+ * Current receive handler.
+ */
+ private Cancelable currentReceive;
+
+ private boolean destroyRequested = false;
+ private final Client client;
+
+
+ public static abstract class Request {
+
+ public boolean onTransmitTimeout() {
+ // per default, just drop the message on timeout!
+ return false;
+ }
+
+ /**
+ * @return true if the request should be kept after the destroy request
+ */
+ public boolean onDestroy() {
+ // per default, do not keep on destroy
+ return false;
+ }
+
+ /**
+ * @return true if the request should be kept after the reconnect
+ */
+ public boolean onReconnect() {
+ // per default, do not keep on reconnect
+ return false;
+ }
+
+ /**
+ * @param alreadyTransmitted true if message has already been sent
over the network
+ */
+ public void onCancel(boolean alreadyTransmitted) {
+ // do nothing
+ }
+
+ public abstract AbsoluteTime getDeadline();
+
+ public abstract void transmit(Connection.MessageSink sink);
+ }
+
+ public RequestQueue(Client client, MessageReceiver receiver) {
+ this.client = client;
+ this.receiver = receiver;
+ }
+
+ /**
+ * Handle next request.
+ */
+ private void handleNextTransmit() {
+ if (currentTransmit != null) {
+ return;
+ }
+
+ final Request request = requestsAwaitingTransmit.poll();
+ if (request == null) {
+ handleReceive();
+ return;
+ }
+
+ AbsoluteTime deadline = request.getDeadline();
+ if (deadline == null) {
+ throw new AssertionError("getDeadline() must return a non-null
AbsoluteTime");
+ }
+
+ currentTransmit = client.notifyTransmitReady(deadline.getRemaining(),
true, 0, new MessageTransmitter() {
+ @Override
+ public void transmit(Connection.MessageSink sink) {
+ currentTransmit = null;
+
+ request.transmit(sink);
+
+ handleReceive();
+ handleNextTransmit();
+ }
+
+ @Override
+ public void handleError() {
+ throw new AssertionError("not implemented");
+ }
+ });
+ }
+
+ private void handleReceive() {
+ if (currentReceive != null || destroyRequested) {
+ return;
+ }
+ currentReceive = client.receive(RelativeTime.FOREVER, new
MessageReceiver() {
+ @Override
+ public void process(GnunetMessage.Body msg) {
+ currentReceive = null;
+
+ receiver.process(msg);
+
+ handleNextTransmit();
+ handleReceive();
+ }
+
+ @Override
+ public void handleError() {
+ receiver.handleError();
+ }
+ });
+ }
+
+ public Cancelable add(final Request request) {
+ allRequests.add(request);
+ requestsAwaitingTransmit.add(request);
+ handleNextTransmit();
+
+ return new Cancelable() {
+ @Override
+ public void cancel() {
+ RequestQueue.this.requestsAwaitingTransmit.remove(request);
+ RequestQueue.this.persistentRequests.remove(request);
+ RequestQueue.this.allRequests.remove(request);
+ request.onCancel(!requestsAwaitingTransmit.contains(request));
+ }
+ };
+ }
+
+
+ public Cancelable addPersistent(final Request request) {
+ persistentRequests.add(request);
+ return add(request);
+ }
+
+ public void reconnect() {
+ client.reconnect();
+ currentReceive = null;
+ currentTransmit = null;
+
+ final LinkedList<Request> remove = new LinkedList<Request>();
+
+
+ for (Request r : allRequests) {
+ boolean keep = r.onReconnect();
+ if (!keep) {
+ remove.add(r);
+ } else {
+ // retransmit an apparently persistent request.
+ if (!requestsAwaitingTransmit.contains(r)) {
+ requestsAwaitingTransmit.add(r);
+ }
+ }
+ }
+ requestsAwaitingTransmit.removeAll(remove);
+ persistentRequests.removeAll(remove);
+ allRequests.removeAll(remove);
+
+ // only transmit, receive should only be called after the first
transmit
+ handleNextTransmit();
+ }
+
+ public void destroy() {
+ destroyRequested = true;
+
+ final LinkedList<Request> remove = new LinkedList<Request>();
+
+ for (Request r : allRequests) {
+ boolean keep = r.onDestroy();
+ if (!keep) {
+ remove.add(r);
+ } else {
+ // retransmit an apparently persistent request.
+ if (!requestsAwaitingTransmit.contains(r)) {
+ requestsAwaitingTransmit.add(r);
+ }
+ }
+ }
+ requestsAwaitingTransmit.removeAll(remove);
+ persistentRequests.removeAll(remove);
+ allRequests.removeAll(remove);
+
+ handleNextTransmit();
+ handleReceive();
+ }
+}
Modified: gnunet-java/src/org/gnunet/util/Scheduler.java
===================================================================
--- gnunet-java/src/org/gnunet/util/Scheduler.java 2012-05-09 09:59:06 UTC
(rev 21369)
+++ gnunet-java/src/org/gnunet/util/Scheduler.java 2012-05-09 10:07:53 UTC
(rev 21370)
@@ -149,6 +149,9 @@
}
private void addChannelEvent(SelectableChannel channel, int eventType)
{
+ if (channel == null) {
+ throw new AssertionError("channel must be non-null");
+ }
if (eventChannels == null) {
eventChannels = new ArrayList<SelectableChannel>();
eventTypes = new ArrayList<Integer>();
@@ -215,6 +218,7 @@
}
private void deregister() {
+ logger.debug("deregistering");
if (eventChannels == null) {
return;
}
@@ -359,7 +363,7 @@
private static RelativeTime handleTimeouts() {
RelativeTime timeout = RelativeTime.FOREVER;
- // check if any timeouts occured
+ // check if any timeouts occurred
while (true) {
TaskConfiguration t = pending.peek();
if (t == null) {
@@ -370,7 +374,6 @@
t.deregister();
t.ctx.reasons = EnumSet.of(Reason.TIMEOUT);
queueReady(t);
- pending.remove(t);
} else {
timeout = remaining;
break;
@@ -406,10 +409,18 @@
for (SelectionKey sk : selector.selectedKeys()) {
TaskConfiguration[] subscribers = (TaskConfiguration[])
sk.attachment();
- if (sk.isReadable()) addSubscriberTask(executableTasks,
subscribers, EVENT_READ);
- if (sk.isWritable()) addSubscriberTask(executableTasks,
subscribers, EVENT_WRITE);
- if (sk.isAcceptable()) addSubscriberTask(executableTasks,
subscribers, EVENT_ACCEPT);
- if (sk.isConnectable()) addSubscriberTask(executableTasks,
subscribers, EVENT_CONNECT);
+ if (sk.isReadable()) {
+ addSubscriberTask(executableTasks, subscribers, EVENT_READ);
+ }
+ if (sk.isWritable()) {
+ addSubscriberTask(executableTasks, subscribers, EVENT_WRITE);
+ }
+ if (sk.isAcceptable()) {
+ addSubscriberTask(executableTasks, subscribers, EVENT_ACCEPT);
+ }
+ if (sk.isConnectable()) {
+ addSubscriberTask(executableTasks, subscribers, EVENT_CONNECT);
+ }
}
for (TaskConfiguration tt : executableTasks) {
@@ -432,6 +443,9 @@
// the gnunet main loop
while (checkLiveness()) {
RelativeTime nextTimeout = handleTimeouts();
+ if (nextTimeout.getMilliseconds() < 0) {
+ logger.warn("negative timeout for select");
+ }
// don't select if there are no tasks; we are done!
if (readyCount == 0 && pending.isEmpty()) {
Modified: gnunet-java/src/org/grothoff/Runabout.java
===================================================================
--- gnunet-java/src/org/grothoff/Runabout.java 2012-05-09 09:59:06 UTC (rev
21369)
+++ gnunet-java/src/org/grothoff/Runabout.java 2012-05-09 10:07:53 UTC (rev
21370)
@@ -20,6 +20,7 @@
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
@@ -28,29 +29,29 @@
* Runabout is a fast implementation of the Walkabout which is a variant of the
* Visitor Pattern that does not require an accept method and uses reflection
* instead.
- * <p>
+ * <p/>
* An instance of Runabout is able to walk over an arbitrary object graph using
* visit methods which take arguments of the specific type of the object to
* visit. For each node in the object graph the Runabout invokes the most
* appropriate visit method.
- * <p>
+ * <p/>
* Using the Runabout typically involves subclassing Runabout and adding a
* couple of visit methods. The Runabout provides a 'visitAppropriate' method
* which will invoke the most appropriate visit method of the current Runabout
* instance. If no visit method is applicable, visitAppropriate calls
- * visitDefault() which, if not overriden, throws an exception.
- * <p>
+ * visitDefault() which, if not overridden, throws an exception.
+ * <p/>
* The elements of the object graph typically extend the Element class, which
* provides a generic way to quickly invoke the Runabout on all the fields of
* the Element.
- * <p>
+ * <p/>
* Note that the Runabout uses dynamic code generation and dynamic loading in
* order to be quickly able to invoke the appropriate visit methods. To make
the
* dynamic code generation fast, the code inlines parts of Java class-files in
* binary form (ugly!).<br>
* A per-thread Cache is used to speed-up the creation of the Runabout by
* caching reflection, code creation and dynamic loading operations.
- * <p>
+ * <p/>
* <bf>Restrictions:</bf> Java semantics require:
* <ul>
* <li>all subclasses must be public, sadly this also means that <strong>you
@@ -59,10 +60,10 @@
* <li>all visit methods must be public (!)</li>
* </ul>
* Otherwise the visitor will die with an IllegalAccessError during execution.
- * <p>
+ * <p/>
*
+ * @author Christian Grothoff
* @version 5.0
- * @author Christian Grothoff
*/
public class Runabout {
@@ -89,9 +90,15 @@
private final Code noCode = new NoCode();
/**
+ * Set when the subclass of the runabout is not public.
+ */
+ private final boolean isPublic;
+
+ /**
* Create a Runabout.
*/
public Runabout() {
+ this.isPublic = Modifier.isPublic(getClass().getModifiers());
map_ = cache_.get(this);
}
@@ -177,7 +184,7 @@
* Find the appropriate Code to call in the map. If no code is found,
return
* null.
*
- * @param c the class for which to find the code
+ * @param c the class for which to find the code
* @param cl the class where to start looking from
* @return the code to run, or null if no code was found
*/
@@ -226,19 +233,42 @@
me = this.getClass();
while (me != null) {
Method[] methods = me.getDeclaredMethods();
- for (Method m : methods) {
+ for (final Method m : methods) {
if ((m.getName().equals("visit"))
&& (!Modifier.isStatic(m.getModifiers()))) {
Class[] args = m.getParameterTypes();
if (args.length != 1)
throw new RunaboutException("Invalid number of
arguments for Runabout in method "
+ m);
- Class viC = args[0];
+ final Class viC = args[0];
if (result.get(viC) != null)
continue;
- Code co = makeCode(viC);
- if (co == null)
- throw new RunaboutException("Could not create/load
dynamic code!");
+ Code co;
+ if (isPublic) {
+ // invoke the visitor with generated code
+ co = makeCode(viC);
+ if (co == null) {
+ throw new RunaboutException("Could not create/load
dynamic code!");
+ }
+ } else {
+ // invoke the visitor with an anonymous inner class,
+ // allows for the runabout to be public as the method
made accessible
+ // by the Method instance.
+ // For Java 7+ the performance of this could be
improved by using a MethodHandle
+ co = new Code() {
+ @Override
+ public void visit(Runabout r, Object o) {
+ try {
+ m.invoke(r, o);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ }
+
result.put(viC, co);
}
@@ -255,11 +285,11 @@
*/
private Code makeCode(Class c) {
byte[] myName // Lovm/util/RunaboutExample; substitute
- = canonicalName(getClass(), false);
+ = canonicalName(getClass(), false);
final int myNameLen = myName.length;
final int myNameLenM2 = myNameLen - 2;
byte[] cName // Ljava/lang/String; substitute
- = canonicalName(c, false);
+ = canonicalName(c, false);
byte[] cNameCast = canonicalName(c, true);
final int cNameLen = cName.length;
final int cNameLenCast = cNameCast.length - 2;
@@ -290,11 +320,11 @@
code[120 + myNameLenM2 + cNameLenCast + 250 - 164 + cNameLen + 1] =
(byte) ')';
code[120 + myNameLenM2 + cNameLenCast + 250 - 164 + cNameLen + 2] =
(byte) 'V';
System.arraycopy(genCodeTemplate,
- 271,
- code,
- 120 + myNameLenM2 + cNameLenCast + 250 - 164
- + cNameLen + 3,
- genCodeTemplate.length - 271);
+ 271,
+ code,
+ 120 + myNameLenM2 + cNameLenCast + 250 - 164
+ + cNameLen + 3,
+ genCodeTemplate.length - 271);
return cache_.loadCode(code, 120 + myNameLenM2 + cNameLenCast + 192
- 164);
}
@@ -367,7 +397,7 @@
Cache() {
loader_ = new ClassLoader() {
public Class<?> loadClass(String name)
- throws ClassNotFoundException {
+ throws ClassNotFoundException {
//noinspection StringEquality
if (name == "Code") // == works here, as both strings are
guaranteed to be interned
return defineClass(null, code, 0, code.length);
@@ -387,14 +417,14 @@
* classname.
*
* @param byteCode the bytecode of the class which must describe a
class
- * of type 'Code'. The class must contain a sequence XXXXXXXX at
- * offset xIdx where the classname is to be patched
- * @param xIdx the index of the XXXXXXXX sequence
+ * of type 'Code'. The class must contain a sequence
XXXXXXXX at
+ * offset xIdx where the classname is to be patched
+ * @param xIdx the index of the XXXXXXXX sequence
* @return an instance of the loaded class, null on error
* @throws ArrayIndexOutOfBoundsException if more than 62<sup>8</sup>
- * classes are loaded :-)
- * @throws RunaboutException if there are problems with dynamic loading
- * of the byteCode
+ * classes are loaded :-)
+ * @throws RunaboutException if there are problems with
dynamic loading
+ * of the byteCode
*/
Code loadCode(byte[] byteCode, int xIdx) {
boolean overflow = true;
@@ -415,7 +445,7 @@
System.arraycopy(lastName_, 0, byteCode, xIdx, 8);
code = byteCode;
- Code co = null;
+ Code co;
try {
co = (Code) loader_.loadClass("Code").newInstance();
} catch (InstantiationException ie) {
@@ -427,6 +457,7 @@
} catch (ClassFormatError cfe) {
throw new RunaboutException(cfe.toString());
} catch (IllegalAccessException iae) {
+ throw new RunaboutException(iae.toString());
}
code = null; // help GC
return co;
@@ -501,7 +532,7 @@
* not to include any debugging information. This is the generated class
* file.
*/
- private final static byte genCodeTemplate[] = { -54, -2, -70, -66, 0, 0, 0,
+ private final static byte genCodeTemplate[] = {-54, -2, -70, -66, 0, 0, 0,
49, 0, 22, 10, 0, 6, 0, 12, 7, 0, 13, 7, 0, 14, 10, 0, 2, 0, 15, 7,
0, 16, 7, 0, 18, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40,
41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 5, 118, 105, 115, 105,
@@ -525,6 +556,15 @@
1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 0, 0, 1, 0, 10, 0, 11,
0, 1, 0, 9, 0, 0, 0, 24, 0, 2, 0, 3, 0, 0, 0, 12, 43, -64, 0, 2,
44, -64, 0, 3, -74, 0, 4, -79, 0, 0, 0, 0, 0, 1, 0, 19, 0, 0, 0,
- 10, 0, 1, 0, 6, 0, 17, 0, 9, 4, 9 }; // GenCodeXXXXXXXX.class
+ 10, 0, 1, 0, 6, 0, 17, 0, 9, 4, 9}; // GenCodeXXXXXXXX.class
+ public static void main(String[] args) {
+ Runabout r = new Runabout() {
+ public void visit(String s) {
+ System.out.println("hi!!");
+ }
+ };
+ r.visitAppropriate("foo");
+ }
+
} // end of Runabout
Added: gnunet-java/test/org/grothoff/RunaboutBenchmark.java
===================================================================
--- gnunet-java/test/org/grothoff/RunaboutBenchmark.java
(rev 0)
+++ gnunet-java/test/org/grothoff/RunaboutBenchmark.java 2012-05-09
10:07:53 UTC (rev 21370)
@@ -0,0 +1,63 @@
+package org.grothoff;
+
+
+public class RunaboutBenchmark {
+
+ public static class MyRunabout extends Runabout {
+ public void visit(String s) {
+
+ }
+ public void visit(Integer i) {
+
+ }
+ }
+
+
+ public static void main(String[] args) {
+ final int runs = 50000000;
+
+ Runabout r = new MyRunabout();
+
+ long start = System.currentTimeMillis();
+
+ Integer integer = 42;
+ for (int i = 0; i < runs; ++i) {
+ r.visitAppropriate("foo");
+ r.visitAppropriate(integer);
+ }
+
+ long end = System.currentTimeMillis();
+
+ long duration1 = end - start;
+
+
+
+ Runabout r2 = new Runabout() {
+ public void visit(String s) {
+
+ }
+ public void visit(Integer i) {
+
+ }
+ };
+
+
+ start = System.currentTimeMillis();
+
+ for (int i = 0; i < runs; ++i) {
+ r2.visitAppropriate("foo");
+ r2.visitAppropriate(integer);
+ }
+
+ end = System.currentTimeMillis();
+
+ long duration2 = end - start;
+
+
+ System.out.println("Runs: " + runs);
+ System.out.println("public: " + duration1);
+ System.out.println("AIC: " + duration2);
+ System.out.println("Overhead: " + duration2 / (double) duration1);
+
+ }
+}
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [GNUnet-SVN] r21370 - in gnunet-java: . src/org/gnunet/statistics src/org/gnunet/util src/org/grothoff test/org test/org/grothoff,
gnunet <=