1616*/
1717
1818using System ;
19+ using System . Threading ;
20+ using System . Threading . Tasks ;
1921using Apache . Arrow . Adbc . Drivers . Apache ;
2022using Apache . Arrow . Adbc . Drivers . Apache . Spark ;
2123using Apache . Arrow . Adbc . Drivers . Apache . Databricks . CloudFetch ;
@@ -26,11 +28,14 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks
2628 /// <summary>
2729 /// Databricks-specific implementation of <see cref="AdbcStatement"/>
2830 /// </summary>
29- internal class DatabricksStatement : SparkStatement , IHiveServer2Statement
31+ internal class DatabricksStatement : SparkStatement , IHiveServer2Statement , IDisposable
3032 {
3133 private bool useCloudFetch ;
3234 private bool canDecompressLz4 ;
3335 private long maxBytesPerFile ;
36+
37+ // Semaphore lock to ensure that polling and fetching results do not both use transport at the same time
38+ private readonly SemaphoreSlim _clientSemaphore = new SemaphoreSlim ( 1 , 1 ) ;
3439
3540 public DatabricksStatement ( DatabricksConnection connection )
3641 : base ( connection )
@@ -70,6 +75,58 @@ public TSparkDirectResults? DirectResults
7075 // Cast the Client to IAsync for CloudFetch compatibility
7176 TCLIService . IAsync IHiveServer2Statement . Client => Connection . Client ;
7277
78+ /// <summary>
79+ /// Executes a client operation in a thread-safe manner.
80+ /// </summary>
81+ /// <typeparam name="TResult">The type of the result.</typeparam>
82+ /// <param name="operation">The operation to execute.</param>
83+ /// <param name="cancellationToken">The cancellation token.</param>
84+ /// <returns>A task representing the asynchronous operation with the result.</returns>
85+ public async Task < TResult > ExecuteClientOperationAsync < TResult > (
86+ Func < TCLIService . IAsync , CancellationToken , Task < TResult > > operation ,
87+ CancellationToken cancellationToken )
88+ {
89+ await _clientSemaphore . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
90+ try
91+ {
92+ return await operation ( Connection . Client , cancellationToken ) . ConfigureAwait ( false ) ;
93+ }
94+ finally
95+ {
96+ _clientSemaphore . Release ( ) ;
97+ }
98+ }
99+
100+ /// <summary>
101+ /// Gets the operation status in a thread-safe manner.
102+ /// </summary>
103+ /// <param name="request">The get operation status request.</param>
104+ /// <param name="cancellationToken">The cancellation token.</param>
105+ /// <returns>A task representing the asynchronous operation with the result.</returns>
106+ public Task < TGetOperationStatusResp > GetOperationStatusAsync (
107+ TGetOperationStatusReq request ,
108+ CancellationToken cancellationToken )
109+ {
110+ return ExecuteClientOperationAsync (
111+ ( client , token ) => client . GetOperationStatus ( request , token ) ,
112+ cancellationToken ) ;
113+ }
114+
115+ /// <summary>
116+ /// Fetches results in a thread-safe manner.
117+ /// </summary>
118+ /// <param name="request">The fetch results request.</param>
119+ /// <param name="cancellationToken">The cancellation token.</param>
120+ /// <returns>A task representing the asynchronous operation with the result.</returns>
121+ public Task < TFetchResultsResp > FetchResultsAsync (
122+ TFetchResultsReq request ,
123+ CancellationToken cancellationToken )
124+ {
125+ return ExecuteClientOperationAsync (
126+ ( client , token ) => client . FetchResults ( request , token ) ,
127+ cancellationToken ) ;
128+ }
129+
73130 public override void SetOption ( string key , string value )
74131 {
75132 switch ( key )
@@ -151,5 +208,18 @@ internal void SetMaxBytesPerFile(long maxBytesPerFile)
151208 {
152209 this . maxBytesPerFile = maxBytesPerFile ;
153210 }
211+
212+ /// <summary>
213+ /// Disposes the resources used by the statement.
214+ /// </summary>
215+ protected override void Dispose ( bool disposing )
216+ {
217+ if ( disposing )
218+ {
219+ _clientSemaphore . Dispose ( ) ;
220+ }
221+
222+ base . Dispose ( disposing ) ;
223+ }
154224 }
155225}
0 commit comments