Hi inksmithy,
Just been through some of my library code to see how I go about it.
Instead of using the asynch methods provided by the Service stubs and relying on the soap toolkit to manage it's own threads I use the synchronous methods and manage the threads myself with a simple thread pool implementation.
Check out the class below. I create a ThreadPool in the main app when it starts up and then when I want to make a call to the API I call addJob(Runnable). Put all the api code in the run method of the runnable but using the synchronous methods.
When procesing the response wrap the final bit of code that updates the Swing GUI into the SwingThread using invokeLater.
Also, it's worth re-using the service/port stubs rather than constructing new ones for every call. (Certainly when using axis2 anyway which is what i use)
public class ThreadPool {
private Set<Thread> threads = new HashSet();
private List<Runnable> jobs = new LinkedList();
public ThreadPool(String name, int threadCount) {
for(int i = 0; i < threadCount; i++) {
Thread t = new WorkerThread("ThreadPool-"+name+"["+i+"]");
threads.add(t);
t.start();
}
}
public void addJob(Runnable job) {
synchronized(jobs) {
jobs.add(job);
jobs.notify();
}
}
class WorkerThread extends Thread {
WorkerThread(String name) {
super(name);
}
public void run() {
while(true) {
Runnable job = null;
synchronized(jobs) {
if(jobs.size() > 0) {
job = jobs.remove(0);
}
else {
try {
jobs.wait();
}
catch(InterruptedException ie) {}
}
}
if(job != null) job.run();
}
}
}
}
I haven't tried it myself, but don't you get into trouble with the potentially overlapping calls and managing the header token? i.e.
Thread1:
get header
invoke API
unpack results
set header // everything ok so far
Thread2
get header
invoke API
unpack results
Thread1
get header
invoke another API
ERROR? Invalid header
Thread2
set header // too late!
Yes, you have to deal with that too. You always have to consider thread safety in your code when there are going to be multiple threads.
If you only 'login' to the API once you only have one 'session' and therefor can only have one call in progress at a time. So if you want multiple threads to be able to access the API simultaneously then you have to login multiple times and manage multiple header tokens.
I've abstracted out a 'Connection' object which tracks the header token for a given session and a 'ConnectionManager' to manage a Set of these (depending on how many I want).
austin, thanks for that! I've been trying to puzzle out the BlockingQueue implementation psmiffy gave me and I was finding it hard to resolve in my head. That threadpool on the other hand is something I can deal I think.
As far as the overlapping calls go, is that simply a matter of putting the calls into synchronised blocks? (Sorry, thats proper spelling, it should be synchronized)
Also, I tend not to use axis, mainly because I couldn't figure it out quickly enough for me to get started with. I know that sounds stupid, but when I tried it, I didn't know enough about how Java works and what class libraries are to make head or tail of it.
Instead, I use the netbeans Web Service Client, which takes the URL of the WSDL and consumes it. It uses JAX-WS and the way calls are implemented is a simple right click. Probably not as good a thing as it sounds, since it means I have only just started to get a handle on what is really within the WSDL and what I can change or not, as the case may be. Netbeans web service consumption is good for using, not good for learning. Same is probably true of most IDEs I think.
Coming back to that threadpool, I'm puzzling away over how to actually use it. Lets say I am only performing GetMarketPrices and GetMarketPricesCompressed calls. In order to keep things straight, I create two classes, called GMPLoop and GMPCLoop. If I create an instance of that threadpool in each of those classes, I I can keep everything nicely separated.
Otherwise, the only real way I can see to do it would be to create the instance as a static object within the main class and access it that way. Is that right?
I'm sorry if these sound like incredibly basic questions. I'm finding concurrency to be a real brain smasher. I know I need to get a handle on it though.
Just as a matter of interest, my main bot has to run with these switches in order to not run out of memory at strange times. I could only assume that it was failing because garbage collection wasn't happening often enough and I was creating huge surges in memory useage.
-Djava.endorsed.dirs="${jaxws.endorsed.dir}" -Xms64m -Xmx512m
Again, thanks for this help - I was beginning to feel like Scotty when Captain Kirk asked him for Warp Speed while running a refrigerator.
Alan
You could have your main method contruct a ThreadPool and assign it to a static instance variable, or better still have it construct a ThreadPool and then pass it into the constructors of your other objects that need it.
When you say 'Run out of memory' do you mean it bombs out completely with on OutOfMemory error? This implies that either your minimum footprint required to run the app excedes the default max heap size or, if it only happens after a while, you have a memory leak. It can't be "beacuse the gc isn't happening enough" ... the vm will gc as and when ... if it gets to the pioint that the heap is full and it can free some space it will. Somewhere the app must be constructing objects and holding onto the references when you aren't expecting it. Best run it through a memory profiler ...
What happens is I will run the app for some time and if I don't have those last two options enabled, the app just vanishes off the screen and I get a warning from netbeans telling me about a Java Result: 134.
There was a bit more info there (not much!), but since I started using those switches I haven't had it happen to me again.
You could have your main method contruct a ThreadPool and assign it toa static instance variable, or better still have it construct aThreadPool and then pass it into the constructors of your other objectsthat need it.
I'll look into which of those works best for me - I'm completely rewriting this bot from the ground up. Even to the point of rewriting the parsers and redesigning the GUI. I couldn't face digging through the old codebase and trying to make sense of it, so I'm starting from scratch and trying to properly comment everything as I do it.
Cheers,
Alan
The simplest thing to do is probably to wrap the entire betfair service in an object which constructs the stubs and tracks a session token and then synchronize all the methods on it.
So eg. lets start by defining a service interface:
public interface BettingService {
boolean login(String username, String password);
}
Then a betfair implementation:
public class BetfairService implements BettingService {
private int productId = 0;
private BFGlobalService_Service service = null;
private BFGlobalService port = null;
private String sessionToken = null;
public BetfairService(int _productId) {
productId = _productId;
service = new BFGlobalService_Service();
port = service.getBFGlobalService();
}
public synchronized boolean login(String _username, String _password) {
LoginReq request = new LoginReq();
request.setUsername(_username);
request.setPassword(_password);
request.setProductId(productId);
LoginResp resp = port.login(request);
sessionToken = resp.getHeader().getSessionToken();
//check the return status and return appropriate value.
}
}
Then all you need to do is extend the interface to include other API calls and implement them making use of the port, sessionToken etc.
Now in your application you have a threadsafe service wrapper that tracks a session which if accessed from multiple threads will work just fine.
My god, I hadn't even thought about doing that!
Do the same for the exchange service and there it goes. Outstanding, thanks very much! Thats actually something I can do on my PDA tomorrow at work while all the usual suspects file into and out of court getting their asbos renewed.
(I work as security at a magistrates court - tuesdays are the days when we get all the regulars in. Honestly, to some of these people, court isn't something to bother them, its a social occasion. We even have the local chavs coming past when they are looking for one of their mates. Unbelievable.)
Thats brilliant austin, thanks very much.
Alan
Yep, throw in an BFExchangeService and Port (or two if you want access to AUS events) in there too and you're away!
Bear in mind that what this effectively gives you is thread-safe access to the API, but doesn't introduce any thread management. ie it allows you to call methods on a BetfairService object from as many threads as you like and they will effectively be queued up and run sequentially. So if any of the calls take a significant length of time everthing else will be delayed.
The solution to this will be to have a pool of BetfairService objects rather than a single one. (Or possibly to use a ThreadLocal instance).
If you only 'login' to the API once you only have one 'session' and therefor can only have one call in progress at a time. So if you want multiple threads to be able to access the API simultaneously then you have to login multiple times and manage multiple header tokens
-austinpodhorzer
Is that really true? I have several threads running (one for prices, one for volumes, one for bets and one for keepalive) and they all 'share' one session. As long as the header token is protected (to be thread-safe), then what's the problem? I've never seen a threading problem with my bot - have I just been lucky?
Each time you make an API call you pass in the previous sessionToken and get a new one back. (apart from login where you get the token for the first time). So each call has to follow the previous one, after you find out the new token.
It follows therefor that to have parallel streams of API calls acting simultaneously you need more that one 'token stream'.
Since you say 'as long as the header token is protected (to be thread-safe)' I guess you are synchronizing around the entire call so that while one thread is using the token the others are blocked until it has finished and updated to the new token? So you too are effectivley serializing the api access.
What you say makes a whole lot of sense, I just can't remember how I coded it up. I'll check tonight, but we may be looking at a good of example of coding whilst under the evil influence of Stella Artois!
Actually ... thinking back I seem to remember that after one of the API major releases they changed things so that the token you get back from the login call doesn't change. ie. you keep getting passed back the same token to reuse.
This may still be the case (will have to turn on some finer logging and check). And if this is the case maybe the API allows you to use the same token in multiple simultaneous calls?
I've just checked and that's the way I've coded it. Every API request uses the session token from my original login (loginResp.header.sessionToken). I'm kinda glad, I thought I was going insane! That's the magic of Stella ;-)
And the betfair side of it is happy to have more than one API call in progress with the same token at the same time?
The way I've set mine up is to pass the sessionToken to the two classes which create request headers for me on every price call. In addition, I have a keepAlive going off every fifteen minutes. As I recall, the documentation says an unattended session will last about twenty minutes before it dies. As you said austin, if you keep making calls to the API, the sessionToken doesn't change except under exceptional circumstances, which they don't explain.
Also, don't think I've ignored all your advice above, I'm still processing it in my head and giving the javadoc a damn good dose of looking at before I come up with more questions.
it seems to be happy with it - after all, twenty requests per second is a lot of requests and some of them are sure to be jumbled up somewhere. If it didn't like it, there would be complaints all over the place.
And the betfair side of it is happy to have more than one API call in progress with the same token at the same time?
-austinpodhorzer
yep, I have four threads (prices, volumes, bets and keepalive) all concurrently using the same session token, no probs at all.
Oh well, that makes life a bit easier then ... which is probably why they did it. :)
Austin, I just wanted to let you know that I just now managed to get that code you gave me running properly and its amazing.
I gave it a starting pool size of ten threads and I'm not sure it has ever got that high yet - I'm still playing with counting how many threads its using and so on. In addition, the difference it is making to the speed of the calls and the responsiveness of the application is nothing short of unreal. I think I still have a lot of optimizing to do and I have to make some decisions as to whether I want to have the makeBet call on its own thread or whether I should just stick it into the threadpool - I think its own dedicated thread would prevent the call from having to wait on other price calls going in, but thats just a matter of experimentation. At the moment though, I have put an artificial limit of 350 milliseconds onto the creation of each call (I don't want to be hit with Betfairs version of the congestion charge) and where I was running GMP and GMPC on their own as seperate threads, I was getting a response every 700 or 800 milliseconds. Now, I seem to be getting a response every 350 milliseconds - unbelievable.
Again, thanks heaps - playing with that code has really helped me understand it.
I know I'm being a bit effusive here, but I was beginning to be a bit despondent about the whole thing - concurrency was really looking harder and harder the more I looked at it. Now if only I could teach my wife that trick, I would have the perfect day! Also, like I wish she would every now and then, I'm starting to get a grasp of it. I think I've used about enough of that metaphor.
Cheers,
Alan
This Topic Is Locked To Guest Posts
It's been a while since this topic was active, if you'd like to get it going again, please post as a registered member