SyncStrategy, the try-catch at line ~218 can go away.
Do we need to check isClosed again during requestRecoveries? It's possible that it's false when the recoveries are being set up, but changes to true later by the time we actually make the RPC.
An optimization might be to break out of the whole loop when closed, since it looks like not much will be happening anyway.
In ZkController, can we log which retry we are on (in both places)? That will make parsing logs easier when this failure happens.
You have a couple System.out.println in SolrCore that could probably be log.debug or even trace.
In the test, you could store (HttpSolrClient) clients.get(0)).getBaseURL() as a local variable instead of loading it twice.
If you're fixing shard count to 2 in the test, do we still want to createCollection(testCollectionName, 1, 3, 1); for three shards?
Architecture question – What happens when you send an update FROMLEADER to the one that happens to be the leader? Also, why are we using decreasing versions?