3

I have recently upgraded my application to :

  • Spring Boot: 2.4.4
  • microsoft-graph: 3.0.0

While upgrading the application i have followed the upgrade guide.

I'm retrieving the group members using below code:

public void getGroupMembersWithDevices(final IGroup group) {
  List<IUser> users = this.userService.getAllUsersWithDevices();
  List<IUser> groupUsers = new ArrayList<>();
  DirectoryObjectCollectionWithReferencesPage directoryObjectCollectionWithReferencesPage = this.graphClient
    .getGraphServiceClient()
    .groups(group.getGroupId().toString())
    .members()
    .buildRequest()
    .select("Id")
    .top(999)
    .get();

    while (directoryObjectCollectionWithReferencesPage != null) {
      final List<DirectoryObject> directoryObjects = directoryObjectCollectionWithReferencesPage.getCurrentPage();
      List<IUser> usersWithDevices = users.stream().filter(
          one -> directoryObjects.stream().anyMatch(two -> UUID.fromString(two.id).equals(one.getUserId())))
          .collect(Collectors.toList());

      if (usersWithDevices.size() > 0) {
        groupUsers.addAll(usersWithDevices);
        users.removeAll(usersWithDevices);
      }

      final DirectoryObjectCollectionWithReferencesRequestBuilder nextPage = 
        directoryObjectCollectionWithReferencesPage.getNextPage();
      if (nextPage == null) {
        break;
      } else {
        directoryObjectCollectionWithReferencesPage = nextPage.buildRequest().get();
      }
    }

    group.setUsers(groupUsers.stream().collect(Collectors.toSet()));
  }

IUser and IDevice are custom models created.

The end goal is to get all the users with devices who are not part of any specified Group. Hence we have first fetched all the users with devices (no errors) and then crosschecked the same against group members.

However, after upgrading the application to I have started getting errors. I have tried rerunning this in production and keep getting the same error at different intervals.

com.microsoft.graph.core.ClientException: Error executing the request
    at com.microsoft.graph.http.CoreHttpProvider.sendRequestInternal(CoreHttpProvider.java:388)
    at com.microsoft.graph.http.CoreHttpProvider.send(CoreHttpProvider.java:214)
    at com.microsoft.graph.http.CoreHttpProvider.send(CoreHttpProvider.java:191)
    at com.microsoft.graph.http.BaseCollectionRequest.send(BaseCollectionRequest.java:102)
    at com.microsoft.graph.http.BaseEntityCollectionRequest.get(BaseEntityCollectionRequest.java:78)
    at com.app.intune.util.GroupUtil.getGroupMembersWithDevices(GroupUtil.java:128)
    at com.app.intune.ScheduleInventory.scheduleGetInventory(ScheduleInventory.java:85)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
  Caused by: java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(Unknown Source)
    at java.net.SocketInputStream.read(Unknown Source)
    at java.net.SocketInputStream.read(Unknown Source)
    at sun.security.ssl.InputRecord.readFully(Unknown Source)
    at sun.security.ssl.InputRecord.read(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
    at sun.security.ssl.AppInputStream.read(Unknown Source)
    at okio.Okio$2.read(Okio.java:140)
    at okio.AsyncTimeout$2.read(AsyncTimeout.java:237)
    at okio.RealBufferedSource.indexOf(RealBufferedSource.java:358)
    at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:230)
    at okhttp3.internal.http1.Http1ExchangeCodec.readHeaderLine(Http1ExchangeCodec.java:242)
    at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.java:213)
    at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.java:115)
    at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:94)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:43)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.RedirectHandler.intercept(RedirectHandler.java:137)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.RetryHandler.intercept(RetryHandler.java:176)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.AuthenticationHandler.intercept(AuthenticationHandler.java:59)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.TelemetryHandler.intercept(TelemetryHandler.java:69)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
    at okhttp3.RealCall.execute(RealCall.java:81)
    at com.microsoft.graph.http.CoreHttpProvider.sendRequestInternal(CoreHttpProvider.java:385)

   ... 20 more

Update 1:
Class created below for creating graph client and setting up proxy:

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.azure.core.http.ProxyOptions;
import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;

@Component
public class GraphClient {
    private String clientId;
    private String clientSecret;
    private List<String> scopes;
    private String tenantId;

    @Autowired
    BasicConfiguration configuration;

    @Autowired
    IntuneConfig intuneConfig;

    @PostConstruct
    public void init() {
        this.clientId = this.configuration.getClientId();
        this.clientSecret = this.configuration.getSecretKey();
        this.scopes = Arrays.asList(this.configuration.getScope());
        this.tenantId = this.configuration.getTenant();
    }

    @SuppressWarnings({ "rawtypes" })
    public GraphServiceClient getGraphServiceClient() {
        final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
                    .clientId(this.clientId).clientSecret(this.clientSecret).tenantId(this.tenantId)
                    .httpClient(
                            new NettyAsyncHttpClientBuilder()
                                    .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(
                                            this.intuneConfig.getProxyHost(), this.intuneConfig.getProxyPort())))
                                    .build())
                    .build();

            final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(this.scopes,
                    clientSecretCredential);

            final GraphServiceClient graphClient = GraphServiceClient.builder()
                    .authenticationProvider(tokenCredentialAuthProvider).buildClient();

            return graphClient;
    }
}

Update 2: Updated GraphClient class to retrieve instance of GraphServiceClient. The instance is created just once.

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.azure.core.http.ProxyOptions;
import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;

@Component
public class GraphClient {
    private String clientId;
    private String clientSecret;
    private List<String> scopes;
    private String tenantId;
    
    @SuppressWarnings("rawtypes")
    private static GraphServiceClient graphClient;

    @Autowired
    BasicConfiguration configuration;

    @Autowired
    IntuneConfig intuneConfig;

    @PostConstruct
    public void init() {
        this.clientId = this.configuration.getClientId();
        this.clientSecret = this.configuration.getSecretKey();
        this.scopes = Arrays.asList(this.configuration.getScope());
        this.tenantId = this.configuration.getTenant();
    }

    @SuppressWarnings({ "rawtypes" })
    public GraphServiceClient getGraphServiceClient() {
    if (GraphClient.graphClient == null) {
        final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
                    .clientId(this.clientId).clientSecret(this.clientSecret).tenantId(this.tenantId)
                    .httpClient(
                            new NettyAsyncHttpClientBuilder()
                                    .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(
                                            this.intuneConfig.getProxyHost(), this.intuneConfig.getProxyPort())))
                                    .build())
                    .build();

            final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(this.scopes,
                    clientSecretCredential);

            final GraphServiceClient client = GraphServiceClient.builder()
                    .authenticationProvider(tokenCredentialAuthProvider).buildClient();

           GraphClient.graphClient = client;
    }
    return GraphClient.graphClient; 
    
    }
}
2
  • can you share the core of the getGraphServiceClient method? I believe you're instantiating the client too often leading to a port exhaustion which in turns leads to time outs. Commented Mar 29, 2021 at 12:42
  • added the class which creates the GraphServiceClient Commented Mar 30, 2021 at 6:07

1 Answer 1

2

The GraphServiceClient is being instantiated for each call. This in turns instantiates one OkHttpClient per GraphServiceClient, which in turns creates a connection pool. Because of the way connections are managed by OSes (they are kept open for a period because closing and opening a connection is a costly operation), this will lead to a port exhaustion on the machine.
The next requests that come in after no more ports are available, block waiting for a port to free and for a connection to be available, and eventually timeout with the exception you are seeing.

To fix that issue:

  • Make sure your GraphClient class is instantiated once throughout the lifecycle of your application
  • Implement some lazy loading for the getGraphServiceClient method, so it "caches" a client in a field and returns that if the value is not null instead of creating a new one for each call.
Sign up to request clarification or add additional context in comments.

15 Comments

thanks, i will give it a try. A small Q: supposing the graphclient class is instantiated once will it refresh the token on expiration of token considering the application might take more time than the expiration of first token?
the authentication provider automatically evaluates whether the token from the cache is still valid, and gets a new one if it's expired before sending the request
I have made sure the GraphClient class is instantiated only once and now not receiving the SocketTimeOutException. Ill keep monitoring for few days to see if the issue comes up again.
Im still getting the SocketTimeOutException after updating the GraphClient as is Update 2. This is intermittent though and does not happen everyday.
It looks like the proxy is not set for the http client the graph client is using, you can do so by customizing the http client to set the proxy
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.