Skip to content

"The semaphore has been disposed" exception on IElasticClient with if-else block or function initializer #7951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
BobbyFerdi opened this issue Sep 20, 2023 · 2 comments
Labels
7.x Relates to a 7.x client version

Comments

@BobbyFerdi
Copy link

BobbyFerdi commented Sep 20, 2023

NEST/Elasticsearch.Net version: 7.17.5

Elasticsearch version: 7.17.12

.NET runtime version: 6.0.22

Operating system version: Windows 11 Home Single Language 22H2

Description of the problem including expected versus actual behavior:
The IElasticClient throws "The semaphore has been disposed" error if its initialization is in if-else block or placed on a function

Steps to reproduce:

  1. Add the following Program.cs in a Web API project
using Elasticsearch.Net;
using Nest;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

if (true)
{
   using var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
   using var connectionSettings = new ConnectionSettings(pool);
   var client = new ElasticClient(connectionSettings);
   builder.Services.AddSingleton<IElasticClient>(client);
}

var app = builder.Build();

app.UseAuthorization();

app.MapControllers();

app.Run();
  1. Use the following block of WeatherForecastController.cs
using APIResearch.Models;
using Microsoft.AspNetCore.Mvc;
using Nest;

namespace APIResearch.Controllers
{
   public class City
   {
       public int? id { get; set; }
       public long? cityId { get; set; }
       public string cityCode { get; set; }
       public string countryCode { get; set; }
       public float? latitude { get; set; }
       public float? longitude { get; set; }
       public string areaType { get; set; }
       public string areaGeometry { get; set; }
       public string supplierCityCode { get; set; }
       public bool isIndonesia { get; set; }
       public int? priority { get; set; }

       [GeoPoint(Name = "location")]
       public Location location { get; set; }

       public string cityName { get; set; }
       public string countryName { get; set; }
       public string language { get; set; }
   }

   [ApiController]
   [Route("[controller]")]
   public class WeatherForecastController : ControllerBase
   {
       private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };


       private readonly IElasticClient _elasticClient;

       public WeatherForecastController(IElasticClient elasticClient) => _elasticClient = elasticClient;

       [HttpGet]
       public async Task<IEnumerable<WeatherForecast>> Get()
       {
           try
           {
               var cityName = "Jakarta";
               var indexName = "jarvis_api_cities_dev";
               var mustQuery = cityName.Split('*')
                   .Select(q => new WildcardQuery { Field = Infer.Field<City>(f => f.cityName), Value = $"{q}*", Rewrite = MultiTermQueryRewrite.ScoringBoolean })
                   .Select(dummy => (QueryContainer)dummy).ToList();
               var searchRequest = new SearchRequest(indexName)
               {
                   Query = new BoolQuery { Must = mustQuery }
               };

               var asd = _elasticClient.Search<City>(searchRequest);
           }
           catch (Exception e)
           {
               //_logger.Error(e, "text");
           }
       }

   }
}
  1. Run the project

Expected behavior
The strange thing is, if I write the Program.cs without using any if-else block like the following:

using Elasticsearch.Net;
using Nest;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

using var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
using var connectionSettings = new ConnectionSettings(pool);
var client = new ElasticClient(connectionSettings);
builder.Services.AddSingleton<IElasticClient>(client);

var app = builder.Build();

app.UseAuthorization();

app.MapControllers();

app.Run();

the exception doesn't occur.

Provide DebugInformation (if relevant):

System.ObjectDisposedException
HResult=0x80131622
Message=The semaphore has been disposed.
Source=System.Private.CoreLib
StackTrace:
at System.Threading.SemaphoreSlim.CheckDispose() in //src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs:line 923
at System.Threading.SemaphoreSlim.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) in /
/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs:line 292
at System.Threading.SemaphoreSlim.Wait(TimeSpan timeout) in /_/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs:line 217
at Elasticsearch.Net.RequestPipeline.FirstPoolUsage(SemaphoreSlim semaphore)
at Elasticsearch.Net.Transport`1.Request[TResponse](HttpMethod method, String path, PostData data, IRequestParameters requestParameters)
at Nest.ElasticClient.Search[TDocument](ISearchRequest request)
at APIResearch.Controllers.WeatherForecastController.d__3.MoveNext() in C:\Users\bobby\source\repos\XXX\XXX.Mobile\APIResearch\Controllers\WeatherForecastController.cs:line 95

This exception was originally thrown at this call stack:
System.Threading.SemaphoreSlim.CheckDispose() in SemaphoreSlim.cs
System.Threading.SemaphoreSlim.Wait(int, System.Threading.CancellationToken) in SemaphoreSlim.cs
System.Threading.SemaphoreSlim.Wait(System.TimeSpan) in SemaphoreSlim.cs
Elasticsearch.Net.RequestPipeline.FirstPoolUsage(System.Threading.SemaphoreSlim)
Elasticsearch.Net.Transport.Request(Elasticsearch.Net.HttpMethod, string, Elasticsearch.Net.PostData, Elasticsearch.Net.IRequestParameters)
Nest.ElasticClient.Search(Nest.ISearchRequest)
APIResearch.Controllers.WeatherForecastController.Get() in WeatherForecastController.cs

Is there any mistake I made on the initialization?

@BobbyFerdi BobbyFerdi added the 7.x Relates to a 7.x client version label Sep 20, 2023
@stevejgordon
Copy link
Contributor

@BobbyFerdi Although disposable, you shouldn't have the using declaration on SingleNodeConnectionPool and ConnectionSettings. These should be treated as singletons for the life of the application.

In this case, the reason for the differing behaviour is down to how the using declaration is scoped. In the first example, the using declaration is scoped to your if conditional block. Once that block ends, before var app = builder.Build();, these objects are disposed of, even though they are used by the ElasticClient added to the service provider. In your working example, there is no conditional block, so the using declaration is scoped to include all of the code, and they do not get disposed of until the application ends.

The solution here is to not apply the using keyword to these. Does that make sense?

@BobbyFerdi
Copy link
Author

ah, you caught me there, @stevejgordon . and thank you for the swift response, too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
7.x Relates to a 7.x client version
Projects
None yet
Development

No branches or pull requests

3 participants