Skip to content

Commit e44d88f

Browse files
committed
Add some level of synchronization to the root of the API
This adds some synchronization to the maps at the root of the API to avoid duplicated calls to the actual GH REST API. Specifically this is targeted around the two maps, orgs and users. This fix makes the GHPRB jenkins plugin behave much better when there are lots of projects that could build for a specific repo (even if only a few are actually triggered)
1 parent d30b040 commit e44d88f

File tree

1 file changed

+60
-25
lines changed

1 file changed

+60
-25
lines changed

src/main/java/org/kohsuke/github/GitHub.java

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ public class GitHub {
7979

8080
private final Map<String,GHUser> users = new Hashtable<String, GHUser>();
8181
private final Map<String,GHOrganization> orgs = new Hashtable<String, GHOrganization>();
82-
8382
private final String apiUrl;
8483

8584
/*package*/ final RateLimitHandler rateLimitHandler;
@@ -226,7 +225,7 @@ public HttpConnector getConnector() {
226225
/**
227226
* Sets the custom connector used to make requests to GitHub.
228227
*/
229-
public void setConnector(HttpConnector connector) {
228+
public synchronized void setConnector(HttpConnector connector) {
230229
this.connector = connector;
231230
}
232231

@@ -276,56 +275,92 @@ public GHRateLimit getRateLimit() throws IOException {
276275
public GHMyself getMyself() throws IOException {
277276
requireCredential();
278277

279-
GHMyself u = retrieve().to("/user", GHMyself.class);
278+
// This entire block is under synchronization to avoid the relatively common case
279+
// where a bunch of threads try to enter this code simultaneously. While we could
280+
// scope the synchronization separately around the map retrieval and update (or use a concurrent hash)
281+
// map, the point is to avoid making unnecessary GH API calls, which are expensive from
282+
// an API rate standpoint
283+
synchronized (users) {
284+
GHUser cachedUser = users.get(this.login);
285+
if (cachedUser != null && cachedUser instanceof GHMyself) {
286+
return (GHMyself)cachedUser;
287+
}
280288

281-
u.root = this;
282-
users.put(u.getLogin(), u);
289+
GHMyself u = retrieve().to("/user", GHMyself.class);
283290

284-
return u;
291+
u.root = this;
292+
users.put(u.getLogin(), u);
293+
return u;
294+
}
285295
}
286296

287297
/**
288298
* Obtains the object that represents the named user.
289299
*/
290300
public GHUser getUser(String login) throws IOException {
291-
GHUser u = users.get(login);
292-
if (u == null) {
293-
u = retrieve().to("/users/" + login, GHUser.class);
294-
u.root = this;
295-
users.put(u.getLogin(), u);
301+
// This entire block is under synchronization to avoid the relatively common case
302+
// where a bunch of threads try to enter this code simultaneously. While we could
303+
// scope the synchronization separately around the map retrieval and update (or use a concurrent hash
304+
// map), the point is to avoid making unnecessary GH API calls, which are expensive from
305+
// an API rate standpoint
306+
synchronized (users) {
307+
GHUser u = users.get(login);
308+
if (u == null) {
309+
u = retrieve().to("/users/" + login, GHUser.class);
310+
u.root = this;
311+
users.put(u.getLogin(), u);
312+
}
313+
return u;
296314
}
297-
return u;
298315
}
299316

300317

301318
/**
302319
* clears all cached data in order for external changes (modifications and del
303320
*/
304321
public void refreshCache() {
305-
users.clear();
306-
orgs.clear();
322+
synchronized (users) {
323+
users.clear();
324+
}
325+
synchronized (orgs) {
326+
orgs.clear();
327+
}
307328
}
308329

309330
/**
310331
* Interns the given {@link GHUser}.
311332
*/
312333
protected GHUser getUser(GHUser orig) throws IOException {
313-
GHUser u = users.get(orig.getLogin());
314-
if (u==null) {
315-
orig.root = this;
316-
users.put(orig.getLogin(),orig);
317-
return orig;
334+
// This entire block is under synchronization to avoid the relatively common case
335+
// where a bunch of threads try to enter this code simultaneously. While we could
336+
// scope the synchronization separately around the map retrieval and update (or use a concurrent hash
337+
// map), the point is to avoid making unnecessary GH API calls, which are expensive from
338+
// an API rate standpoint
339+
synchronized (users) {
340+
GHUser u = users.get(orig.getLogin());
341+
if (u==null) {
342+
orig.root = this;
343+
users.put(orig.getLogin(),orig);
344+
return orig;
345+
}
346+
return u;
318347
}
319-
return u;
320348
}
321349

322350
public GHOrganization getOrganization(String name) throws IOException {
323-
GHOrganization o = orgs.get(name);
324-
if (o==null) {
325-
o = retrieve().to("/orgs/" + name, GHOrganization.class).wrapUp(this);
326-
orgs.put(name,o);
351+
// This entire block is under synchronization to avoid the relatively common case
352+
// where a bunch of threads try to enter this code simultaneously. While we could
353+
// scope the synchronization separately around the map retrieval and update (or use a concurrent hash
354+
// map), the point is to avoid making unnecessary GH API calls, which are expensive from
355+
// an API rate standpoint
356+
synchronized (orgs) {
357+
GHOrganization o = orgs.get(name);
358+
if (o==null) {
359+
o = retrieve().to("/orgs/" + name, GHOrganization.class).wrapUp(this);
360+
orgs.put(name,o);
361+
}
362+
return o;
327363
}
328-
return o;
329364
}
330365

331366
/**

0 commit comments

Comments
 (0)