Nodejs Confluence plugin using async module never calling final callback

Enrique Cadalso April 8, 2014

Hello,

I am trying to create an ondemand plugin using atlassian-connect-express, nodejs, the JSON-RPC Confluence API and the async module for working with asynchronous JavaScript.

I have managed to get the user and group list from the target Confluence application using the JSON-RPC Confluence API. However when using the async.each method it never reaches the final callback function to be executed when all the iterations have been done.

Using this code it calls the first callback function for each iteration of the users array, but it never calls the final callback function.

//users: array of users
//membershipList: multi dimensional array of users and its groups.

async.each(users, function(currentuser, callback) { 

	//For each user invokes getUserGroups 
	console.log("Getting group list for user " + currentuser + " (getUserGroups JSON-RPC API)");
	
  addon.httpClient(req).post({
    uri: "/rpc/json-rpc/confluenceservice-v2/getUserGroups",
    json: [currentuser],
    userId: userId
    }, function(err, resp, body) {  //First callback function for each iteration
      var jsonRpcBody = body;
      if (!err && !jsonRpcBody.error) {

			// If no errors in getUserGroups 
			console.log("Groups found for user " + currentuser + ": " + JSON.stringify(body) );
			
			// Do something with the user and its groups.
			UpdateUserMembershipList(currentuser,membershipList)
			
    } else {
      console.log("Error getting user membership ", err, jsonRpcBody.error);
      res.render('error', {error: err} );
    }
  }); //getUserGroups	


}, function(err) {   //Final callback function when all iterations are done
    if (err) return next(err);
		
		// When async iteration is done and all user group membership found
		console.log("User membership queries finished");
		DoSomethingWithUserMembership(membershipList);  //It never gets here

});  //async

In the nodejs console I get the console.log output for every iteration, e.g.

Groups found for user admin: ["confluence-administrators","confluence-users"]
Groups found for user testuser: ["confluence-users"]

However it never executes the line (No. 35 in the pasted code).

DoSomethingWithUserMembership(membershipList);

In the Confluence log it shows the error

2014-04-08 19:03:50,593 ERROR [http-1990-6] [plugin.module.confluence.MacroContentManager] getStaticContent Could not render macro
 -- url: /confluence/display/ds/Test+my+macro+4 | page: 852017 | userName: admin | referer: http://macbookserver.local:1990/confluence/dashboard.action | action: viewpage
com.atlassian.plugin.connect.plugin.util.http.ContentRetrievalException: java.lang.RuntimeException: java.net.SocketTimeoutException
	at com.atlassian.plugin.connect.plugin.util.http.CachingHttpContentRetriever$FailFunction.apply(CachingHttpContentRetriever.java:243)
	at com.atlassian.plugin.connect.plugin.util.http.CachingHttpContentRetriever$FailFunction.apply(CachingHttpContentRetriever.java:227)
	at com.atlassian.util.concurrent.Promises$Of$2.apply(Promises.java:259)
	at com.atlassian.util.concurrent.Promises$Of$2.apply(Promises.java:256)
	at com.atlassian.util.concurrent.Promises$2.onFailure(Promises.java:162)
	at com.google.common.util.concurrent.Futures$7.run(Futures.java:1074)
	at com.google.common.util.concurrent.MoreExecutors$SameThreadExecutorService.execute(MoreExecutors.java:253)
	at com.google.common.util.concurrent.ExecutionList$RunnableExecutorPair.execute(ExecutionList.java:161)
	at com.google.common.util.concurrent.ExecutionList.execute(ExecutionList.java:146)
	at com.google.common.util.concurrent.AbstractFuture.done(AbstractFuture.java:235)
	at com.google.common.util.concurrent.AbstractFuture.setException(AbstractFuture.java:190)
	at com.google.common.util.concurrent.SettableFuture.setException(SettableFuture.java:68)
	at com.atlassian.util.concurrent.Promises$Of$2.apply(Promises.java:261)
	at com.atlassian.util.concurrent.Promises$Of$2.apply(Promises.java:256)
	at com.atlassian.util.concurrent.Promises$2.onFailure(Promises.java:162)
	at com.google.common.util.concurrent.Futures$7.run(Futures.java:1074)
	at com.google.common.util.concurrent.MoreExecutors$SameThreadExecutorService.execute(MoreExecutors.java:253)
	at com.google.common.util.concurrent.ExecutionList$RunnableExecutorPair.execute(ExecutionList.java:161)
	at com.google.common.util.concurrent.ExecutionList.execute(ExecutionList.java:146)
	at com.google.common.util.concurrent.AbstractFuture.done(AbstractFuture.java:235)
	at com.google.common.util.concurrent.AbstractFuture.setException(AbstractFuture.java:190)
	at com.google.common.util.concurrent.SettableFuture.setException(SettableFuture.java:68)
	at com.atlassian.httpclient.apache.httpcomponents.SettableFuturePromiseHttpPromiseAsyncClient$1$2.run(SettableFuturePromiseHttpPromiseAsyncClient.java:59)
	at com.atlassian.httpclient.apache.httpcomponents.SettableFuturePromiseHttpPromiseAsyncClient$ThreadLocalDelegateRunnable$1.run(SettableFuturePromiseHttpPromiseAsyncClient.java:197)
	at com.atlassian.httpclient.apache.httpcomponents.SettableFuturePromiseHttpPromiseAsyncClient.runInContext(SettableFuturePromiseHttpPromiseAsyncClient.java:90)
	at com.atlassian.httpclient.apache.httpcomponents.SettableFuturePromiseHttpPromiseAsyncClient$ThreadLocalDelegateRunnable.run(SettableFuturePromiseHttpPromiseAsyncClient.java:192)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
	at java.lang.Thread.run(Thread.java:680)
Caused by: java.lang.RuntimeException: java.net.SocketTimeoutException
	at com.google.common.base.Throwables.propagate(Throwables.java:156)
	at com.atlassian.httpclient.apache.httpcomponents.ApacheAsyncHttpClient$5.apply(ApacheAsyncHttpClient.java:335)
	at com.atlassian.httpclient.apache.httpcomponents.ApacheAsyncHttpClient$5.apply(ApacheAsyncHttpClient.java:329)
	at com.atlassian.util.concurrent.Promises$Of$2.apply(Promises.java:259)
	... 16 more
Caused by: java.net.SocketTimeoutException
	at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.timeout(HttpAsyncRequestExecutor.java:279)
	at org.apache.http.impl.nio.client.LoggingAsyncRequestExecutor.timeout(LoggingAsyncRequestExecutor.java:128)
	at org.apache.http.impl.nio.DefaultHttpClientIODispatch.onTimeout(DefaultHttpClientIODispatch.java:136)
	at org.apache.http.impl.nio.DefaultHttpClientIODispatch.onTimeout(DefaultHttpClientIODispatch.java:49)
	at org.apache.http.impl.nio.reactor.AbstractIODispatch.timeout(AbstractIODispatch.java:169)
	at org.apache.http.impl.nio.reactor.BaseIOReactor.sessionTimedOut(BaseIOReactor.java:257)
	at org.apache.http.impl.nio.reactor.AbstractIOReactor.timeoutCheck(AbstractIOReactor.java:494)
	at org.apache.http.impl.nio.reactor.BaseIOReactor.validate(BaseIOReactor.java:207)
	at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:284)
	at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:106)
	at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:604)
	... 1 more

Any clues about the java.net.SocketTimeoutException error?


Any other solution to iterate the users array requesting the groups of each user and waiting for all users being processed (preferably in a non blocking way) before processing all the resulting information?

Thanks

1 answer

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

2 votes
Answer accepted
seb
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 8, 2014

The SocketTimeoutException is occurring because Confluence has determined that your macro is not returning data within the allotted timeframe.

Here are some options:

- Make your code run/return faster - I'm not being facetious here :)

- Convert your macro to immediately return a "loading" function which makes an asynchronous request back to your add-on and retrieve data as it is ready. You could consider using websockets here if the data is big.

Regarding your initial question about async#each. Reading the documentation:

  • iterator(item, callback) - A function to apply to each item in arr. The iterator is passed a callback(err) which must be called once it has completed. If no error has occured, the callback should be run without arguments or with an explicit null argument.

You need to invoke the callback yourself from each iteration. Not doing so means that the async module doesn't know when all the iterations have completed, and thus your "un-ending" request.

Enrique Cadalso April 8, 2014

Hello Seb,

You are quite right, I forgot to call the callback function. I will change my code and post the solution here, in case it helps others.

Thanks a lot for your help.

Enrique Cadalso April 8, 2014

Hi Seb,

As you said, including callback() after line 21 it works fine, no more SocketTimeoutException and the final callback function is called. Full code would be

//users: array of users
//membershipList: multi dimensional array of users and its groups.
 
async.each(users, function(currentuser, callback) {
 
    //For each user invokes getUserGroups
    console.log("Getting group list for user " + currentuser + " (getUserGroups JSON-RPC API)");
    
  addon.httpClient(req).post({
    uri: "/rpc/json-rpc/confluenceservice-v2/getUserGroups",
    json: [currentuser],
    userId: userId
    }, function(err, resp, body) {  //First callback function for each iteration
      var jsonRpcBody = body;
      if (!err && !jsonRpcBody.error) {
 
            // If no errors in getUserGroups
            console.log("Groups found for user " + currentuser + ": " + JSON.stringify(body) );
            
            // Do something with the user and its groups.
            UpdateUserMembershipList(currentuser,membershipList)

            callback();

    } else {
      console.log("Error getting user membership ", err, jsonRpcBody.error);
      res.render('error', {error: err} );
    }
  }); //getUserGroups  
 
 
}, function(err) {   //Final callback function when all iterations are done
    if (err) return next(err);
        
        // When async iteration is done and all user group membership found
        console.log("User membership queries finished");
        DoSomethingWithUserMembership(membershipList); 
 
});  //async

Thanks again for your help.

Cheers,

Enrique.

TAGS
AUG Leaders

Atlassian Community Events