@@ -41,30 +41,22 @@ public sealed class OsduClient : IDisposable
4141 private readonly OsduConfig _config ;
4242 private readonly ITokenProvider _tokenProvider ;
4343 private readonly ILoggerFactory _loggerFactory ;
44+
45+ /// <summary>
46+ /// Guards all lazy initialisation state below. Service clients and their adapters are
47+ /// built on first access, so concurrent first calls would otherwise race on the
48+ /// dictionaries and on <see cref="_httpClients"/>. Contention is negligible: each
49+ /// service is built at most once per client instance.
50+ /// </summary>
51+ private readonly Lock _sync = new ( ) ;
52+
4453 private readonly List < HttpClient > _httpClients = [ ] ;
4554 private readonly Dictionary < string , HttpClientRequestAdapter > _adapters = [ ] ;
46- private bool _disposed ;
4755
48- private CrsCatalogClient ? _crsCatalog ;
49- private CrsConversionClient ? _crsConversion ;
50- private DatasetClient ? _dataset ;
51- private EntitlementsClient ? _entitlements ;
52- private FileClient ? _file ;
53- private GeospatialClient ? _geospatial ;
54- private IndexerClient ? _indexer ;
55- private LegalClient ? _legal ;
56- private NotificationClient ? _notification ;
57- private PartitionClient ? _partition ;
58- private PolicyClient ? _policy ;
59- private RegisterClient ? _register ;
60- private SchemaClient ? _schema ;
61- private SearchClient ? _search ;
62- private SeismicDdmsClient ? _seismicDdms ;
63- private StorageClient ? _storage ;
64- private UnitClient ? _unit ;
65- private WellboreDdmsClient ? _wellboreDdms ;
66- private WorkflowClient ? _workflow ;
67- private WellboreDdmsBulkClient ? _wellboreDdmsBulk ;
56+ /// <summary>Built service clients, keyed by client type. Guarded by <see cref="_sync"/>.</summary>
57+ private readonly Dictionary < Type , object > _clients = [ ] ;
58+
59+ private bool _disposed ;
6860
6961 /// <param name="config">OSDU configuration. Use <see cref="OsduConfig.FromConfiguration"/> to bind from <c>IConfiguration</c>.</param>
7062 /// <param name="tokenProvider">
@@ -83,34 +75,33 @@ public OsduClient(OsduConfig config, ITokenProvider? tokenProvider = null, ILogg
8375 _loggerFactory = loggerFactory ?? NullLoggerFactory . Instance ;
8476 }
8577
86- public CrsCatalogClient CrsCatalog => _crsCatalog ??= Build ( ref _crsCatalog , "crs_catalog" ) ;
87- public CrsConversionClient CrsConversion => _crsConversion ??= Build ( ref _crsConversion , "crs_conversion" ) ;
88- public DatasetClient Dataset => _dataset ??= Build ( ref _dataset , "dataset" ) ;
89- public EntitlementsClient Entitlements => _entitlements ??= Build ( ref _entitlements , "entitlements" ) ;
90- public FileClient File => _file ??= Build ( ref _file , "file" ) ;
91- public GeospatialClient Geospatial => _geospatial ??= Build ( ref _geospatial , "geospatial" ) ;
92- public IndexerClient Indexer => _indexer ??= Build ( ref _indexer , "indexer" ) ;
93- public LegalClient Legal => _legal ??= Build ( ref _legal , "legal" ) ;
94- public NotificationClient Notification => _notification ??= Build ( ref _notification , "notification" ) ;
95- public PartitionClient Partition => _partition ??= Build ( ref _partition , "partition" ) ;
96- public PolicyClient Policy => _policy ??= Build ( ref _policy , "policy" ) ;
97- public RegisterClient Register => _register ??= Build ( ref _register , "register" ) ;
98- public SchemaClient Schema => _schema ??= Build ( ref _schema , "schema" ) ;
99- public SearchClient Search => _search ??= Build ( ref _search , "search" ) ;
100- public SeismicDdmsClient SeismicDdms => _seismicDdms ??= Build ( ref _seismicDdms , "seismic_ddms" ) ;
101- public StorageClient Storage => _storage ??= Build ( ref _storage , "storage" ) ;
102- public UnitClient Unit => _unit ??= Build ( ref _unit , "unit" ) ;
103- public WellboreDdmsClient WellboreDdms => _wellboreDdms ??= Build ( ref _wellboreDdms , "wellbore_ddms" ) ;
104- public WorkflowClient Workflow => _workflow ??= Build ( ref _workflow , "workflow" ) ;
78+ public CrsCatalogClient CrsCatalog => Client < CrsCatalogClient > ( "crs_catalog" ) ;
79+ public CrsConversionClient CrsConversion => Client < CrsConversionClient > ( "crs_conversion" ) ;
80+ public DatasetClient Dataset => Client < DatasetClient > ( "dataset" ) ;
81+ public EntitlementsClient Entitlements => Client < EntitlementsClient > ( "entitlements" ) ;
82+ public FileClient File => Client < FileClient > ( "file" ) ;
83+ public GeospatialClient Geospatial => Client < GeospatialClient > ( "geospatial" ) ;
84+ public IndexerClient Indexer => Client < IndexerClient > ( "indexer" ) ;
85+ public LegalClient Legal => Client < LegalClient > ( "legal" ) ;
86+ public NotificationClient Notification => Client < NotificationClient > ( "notification" ) ;
87+ public PartitionClient Partition => Client < PartitionClient > ( "partition" ) ;
88+ public PolicyClient Policy => Client < PolicyClient > ( "policy" ) ;
89+ public RegisterClient Register => Client < RegisterClient > ( "register" ) ;
90+ public SchemaClient Schema => Client < SchemaClient > ( "schema" ) ;
91+ public SearchClient Search => Client < SearchClient > ( "search" ) ;
92+ public SeismicDdmsClient SeismicDdms => Client < SeismicDdmsClient > ( "seismic_ddms" ) ;
93+ public StorageClient Storage => Client < StorageClient > ( "storage" ) ;
94+ public UnitClient Unit => Client < UnitClient > ( "unit" ) ;
95+ public WellboreDdmsClient WellboreDdms => Client < WellboreDdmsClient > ( "wellbore_ddms" ) ;
96+ public WorkflowClient Workflow => Client < WorkflowClient > ( "workflow" ) ;
10597
10698 /// <summary>
10799 /// Hand-written Wellbore DDMS bulk-data helpers for <c>application/x-parquet</c>
108100 /// (read, write, and chunked session writes), which the generated
109101 /// <see cref="WellboreDdms"/> client cannot express. Shares the same
110102 /// authenticated transport as <see cref="WellboreDdms"/>.
111103 /// </summary>
112- public WellboreDdmsBulkClient WellboreDdmsBulk =>
113- _wellboreDdmsBulk ??= new WellboreDdmsBulkClient ( GetOrCreateAdapter ( "wellbore_ddms" ) ) ;
104+ public WellboreDdmsBulkClient WellboreDdmsBulk => Client < WellboreDdmsBulkClient > ( "wellbore_ddms" ) ;
114105
115106 /// <summary>
116107 /// Returns the authenticated Kiota request adapter for the given service attr name
@@ -121,20 +112,39 @@ public OsduClient(OsduConfig config, ITokenProvider? tokenProvider = null, ILogg
121112 /// </summary>
122113 public IRequestAdapter GetRequestAdapter ( string serviceAttr ) => GetOrCreateAdapter ( serviceAttr ) ;
123114
124- private T Build < T > ( ref T ? field , string serviceAttr ) where T : class
115+ /// <summary>
116+ /// Returns the cached service client of type <typeparamref name="T"/>, building it
117+ /// (and its adapter) on first access. Keyed by client type rather than by
118+ /// <paramref name="serviceAttr"/> so that two client types may share one adapter —
119+ /// <see cref="WellboreDdms"/> and <see cref="WellboreDdmsBulk"/> both use
120+ /// <c>wellbore_ddms</c>.
121+ /// </summary>
122+ private T Client < T > ( string serviceAttr ) where T : class
125123 {
126- if ( field is not null ) return field ;
127- var adapter = GetOrCreateAdapter ( serviceAttr ) ;
128- field = ( T ) Activator . CreateInstance ( typeof ( T ) , adapter ) ! ;
129- return field ;
124+ lock ( _sync )
125+ {
126+ ObjectDisposedException . ThrowIf ( _disposed , this ) ;
127+
128+ if ( _clients . TryGetValue ( typeof ( T ) , out var existing ) ) return ( T ) existing ;
129+
130+ var client = ( T ) Activator . CreateInstance ( typeof ( T ) , GetOrCreateAdapter ( serviceAttr ) ) ! ;
131+ _clients [ typeof ( T ) ] = client ;
132+ return client ;
133+ }
130134 }
131135
132136 private HttpClientRequestAdapter GetOrCreateAdapter ( string serviceAttr )
133137 {
134- if ( _adapters . TryGetValue ( serviceAttr , out var adapter ) ) return adapter ;
135- adapter = CreateAdapter ( _config . UrlFor ( serviceAttr ) ) ;
136- _adapters [ serviceAttr ] = adapter ;
137- return adapter ;
138+ lock ( _sync )
139+ {
140+ ObjectDisposedException . ThrowIf ( _disposed , this ) ;
141+
142+ if ( _adapters . TryGetValue ( serviceAttr , out var adapter ) ) return adapter ;
143+
144+ adapter = CreateAdapter ( _config . UrlFor ( serviceAttr ) ) ;
145+ _adapters [ serviceAttr ] = adapter ;
146+ return adapter ;
147+ }
138148 }
139149
140150 /// <summary>
@@ -168,11 +178,18 @@ private HttpClientRequestAdapter CreateAdapter(string baseUrl)
168178
169179 public void Dispose ( )
170180 {
171- if ( _disposed ) return ;
172- _disposed = true ;
173- foreach ( var client in _httpClients )
174- client . Dispose ( ) ;
175- _httpClients . Clear ( ) ;
181+ lock ( _sync )
182+ {
183+ if ( _disposed ) return ;
184+ _disposed = true ;
185+
186+ foreach ( var client in _httpClients )
187+ client . Dispose ( ) ;
188+
189+ _httpClients . Clear ( ) ;
190+ _adapters . Clear ( ) ;
191+ _clients . Clear ( ) ;
192+ }
176193 }
177194
178195 /// <summary>Adapts <see cref="ITokenProvider"/> to Kiota's <see cref="IAccessTokenProvider"/>.</summary>
0 commit comments