< Summary

Information
Class: Ice.Internal.ConnectionFlushBatchAsync
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Internal/OutgoingAsync.cs
Tag: 71_18251537082
Line coverage
87%
Covered lines: 27
Uncovered lines: 4
Coverable lines: 31
Total lines: 1572
Line coverage: 87%
Branch coverage
87%
Covered branches: 14
Total branches: 16
Branch coverage: 87.5%
Method coverage
100%
Covered methods: 2
Total methods: 2
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
invoke(...)87.5%16.611686.67%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/Internal/OutgoingAsync.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4
 5namespace Ice.Internal;
 6
 7public interface OutgoingAsyncCompletionCallback
 8{
 9    void init(OutgoingAsyncBase og);
 10
 11    bool handleSent(bool done, bool alreadySent, OutgoingAsyncBase og);
 12
 13    bool handleException(Ice.Exception ex, OutgoingAsyncBase og);
 14
 15    bool handleResponse(bool userThread, bool ok, OutgoingAsyncBase og);
 16
 17    void handleInvokeSent(bool sentSynchronously, bool done, bool alreadySent, OutgoingAsyncBase og);
 18
 19    void handleInvokeException(Ice.Exception ex, OutgoingAsyncBase og);
 20
 21    void handleInvokeResponse(bool ok, OutgoingAsyncBase og);
 22}
 23
 24public abstract class OutgoingAsyncBase
 25{
 26    public virtual bool sent() => sentImpl(true);
 27
 28    public virtual bool exception(Ice.Exception ex) => exceptionImpl(ex);
 29
 30    public virtual bool response()
 31    {
 32        Debug.Assert(false); // Must be overridden by request that can handle responses
 33        return false;
 34    }
 35
 36    public void invokeSentAsync()
 37    {
 38        //
 39        // This is called when it's not safe to call the sent callback
 40        // synchronously from this thread. Instead the exception callback
 41        // is called asynchronously from the client thread pool.
 42        //
 43        try
 44        {
 45            instance_.clientThreadPool().execute(invokeSent, cachedConnection_);
 46        }
 47        catch (Ice.CommunicatorDestroyedException)
 48        {
 49        }
 50    }
 51
 52    public void invokeExceptionAsync() =>
 53        //
 54        // CommunicatorDestroyedCompleted is the only exception that can propagate directly
 55        // from this method.
 56        //
 57        instance_.clientThreadPool().execute(invokeException, cachedConnection_);
 58
 59    public void invokeResponseAsync() =>
 60        //
 61        // CommunicatorDestroyedCompleted is the only exception that can propagate directly
 62        // from this method.
 63        //
 64        instance_.clientThreadPool().execute(invokeResponse, cachedConnection_);
 65
 66    public void invokeSent()
 67    {
 68        try
 69        {
 70            _completionCallback.handleInvokeSent(sentSynchronously_, _doneInSent, _alreadySent, this);
 71        }
 72        catch (System.Exception ex)
 73        {
 74            warning(ex);
 75        }
 76
 77        if (observer_ != null && _doneInSent)
 78        {
 79            observer_.detach();
 80            observer_ = null;
 81        }
 82    }
 83
 84    public void invokeException()
 85    {
 86        try
 87        {
 88            try
 89            {
 90                throw _ex;
 91            }
 92            catch (Ice.Exception ex)
 93            {
 94                _completionCallback.handleInvokeException(ex, this);
 95            }
 96        }
 97        catch (System.Exception ex)
 98        {
 99            warning(ex);
 100        }
 101
 102        if (observer_ != null)
 103        {
 104            observer_.detach();
 105            observer_ = null;
 106        }
 107    }
 108
 109    public void invokeResponse()
 110    {
 111        if (_ex != null)
 112        {
 113            invokeException();
 114            return;
 115        }
 116
 117        try
 118        {
 119            try
 120            {
 121                _completionCallback.handleInvokeResponse((state_ & StateOK) != 0, this);
 122            }
 123            catch (Ice.Exception ex)
 124            {
 125                if (_completionCallback.handleException(ex, this))
 126                {
 127                    _completionCallback.handleInvokeException(ex, this);
 128                }
 129            }
 130            catch (System.AggregateException ex)
 131            {
 132                throw ex.InnerException;
 133            }
 134        }
 135        catch (System.Exception ex)
 136        {
 137            warning(ex);
 138        }
 139
 140        if (observer_ != null)
 141        {
 142            observer_.detach();
 143            observer_ = null;
 144        }
 145    }
 146
 147    public virtual void cancelable(CancellationHandler handler)
 148    {
 149        lock (mutex_)
 150        {
 151            if (_cancellationException != null)
 152            {
 153                try
 154                {
 155                    throw _cancellationException;
 156                }
 157                catch (Ice.LocalException)
 158                {
 159                    _cancellationException = null;
 160                    throw;
 161                }
 162            }
 163            _cancellationHandler = handler;
 164        }
 165    }
 166
 167    public void cancel() => cancel(new Ice.InvocationCanceledException());
 168
 169    public void attachRemoteObserver(Ice.ConnectionInfo info, Ice.Endpoint endpt, int requestId)
 170    {
 171        Ice.Instrumentation.InvocationObserver observer = getObserver();
 172        if (observer != null)
 173        {
 174            int size = os_.size() - Protocol.headerSize - 4;
 175            childObserver_ = observer.getRemoteObserver(info, endpt, requestId, size);
 176            childObserver_?.attach();
 177        }
 178    }
 179
 180    public void attachCollocatedObserver(Ice.ObjectAdapter adapter, int requestId)
 181    {
 182        Ice.Instrumentation.InvocationObserver observer = getObserver();
 183        if (observer != null)
 184        {
 185            int size = os_.size() - Protocol.headerSize - 4;
 186            childObserver_ = observer.getCollocatedObserver(adapter, requestId, size);
 187            childObserver_?.attach();
 188        }
 189    }
 190
 191    public Ice.OutputStream getOs() => os_;
 192
 193    public Ice.InputStream getIs() => is_;
 194
 195    public virtual void throwUserException()
 196    {
 197    }
 198
 199    public virtual void cacheMessageBuffers()
 200    {
 201    }
 202
 203    public bool isSynchronous() => synchronous_;
 204
 205    protected OutgoingAsyncBase(
 206        Instance instance,
 207        OutgoingAsyncCompletionCallback completionCallback,
 208        Ice.OutputStream os = null,
 209        Ice.InputStream iss = null)
 210    {
 211        instance_ = instance;
 212        sentSynchronously_ = false;
 213        synchronous_ = false;
 214        _doneInSent = false;
 215        _alreadySent = false;
 216        state_ = 0;
 217        os_ = os ?? new OutputStream(Ice.Util.currentProtocolEncoding, instance.defaultsAndOverrides().defaultFormat);
 218        is_ = iss ?? new Ice.InputStream(instance, Ice.Util.currentProtocolEncoding);
 219        _completionCallback = completionCallback;
 220        _completionCallback?.init(this);
 221    }
 222
 223    protected virtual bool sentImpl(bool done)
 224    {
 225        lock (mutex_)
 226        {
 227            _alreadySent = (state_ & StateSent) > 0;
 228            state_ |= StateSent;
 229            if (done)
 230            {
 231                _doneInSent = true;
 232                if (childObserver_ != null)
 233                {
 234                    childObserver_.detach();
 235                    childObserver_ = null;
 236                }
 237                _cancellationHandler = null;
 238
 239                //
 240                // For oneway requests after the data has been sent
 241                // the buffers can be reused unless this is a
 242                // collocated invocation. For collocated invocations
 243                // the buffer won't be reused because it has already
 244                // been marked as cached in invokeCollocated.
 245                //
 246                cacheMessageBuffers();
 247            }
 248
 249            bool invoke = _completionCallback.handleSent(done, _alreadySent, this);
 250            if (!invoke && _doneInSent && observer_ != null)
 251            {
 252                observer_.detach();
 253                observer_ = null;
 254            }
 255            return invoke;
 256        }
 257    }
 258
 259    protected virtual bool exceptionImpl(Ice.Exception ex)
 260    {
 261        lock (mutex_)
 262        {
 263            _ex = ex;
 264            if (childObserver_ != null)
 265            {
 266                childObserver_.failed(ex.ice_id());
 267                childObserver_.detach();
 268                childObserver_ = null;
 269            }
 270            _cancellationHandler = null;
 271
 272            observer_?.failed(ex.ice_id());
 273            bool invoke = _completionCallback.handleException(ex, this);
 274            if (!invoke && observer_ != null)
 275            {
 276                observer_.detach();
 277                observer_ = null;
 278            }
 279            return invoke;
 280        }
 281    }
 282
 283    protected virtual bool responseImpl(bool userThread, bool ok, bool invoke)
 284    {
 285        lock (mutex_)
 286        {
 287            if (ok)
 288            {
 289                state_ |= StateOK;
 290            }
 291
 292            _cancellationHandler = null;
 293
 294            try
 295            {
 296                invoke &= _completionCallback.handleResponse(userThread, ok, this);
 297            }
 298            catch (Ice.Exception ex)
 299            {
 300                _ex = ex;
 301                invoke = _completionCallback.handleException(ex, this);
 302            }
 303            if (!invoke && observer_ != null)
 304            {
 305                observer_.detach();
 306                observer_ = null;
 307            }
 308            return invoke;
 309        }
 310    }
 311
 312    protected void cancel(Ice.LocalException ex)
 313    {
 314        CancellationHandler handler;
 315        {
 316            lock (mutex_)
 317            {
 318                if (_cancellationHandler == null)
 319                {
 320                    _cancellationException = ex;
 321                    return;
 322                }
 323                handler = _cancellationHandler;
 324            }
 325        }
 326        handler.asyncRequestCanceled(this, ex);
 327    }
 328
 329    private void warning(System.Exception ex)
 330    {
 331        if (instance_.initializationData().properties.getIcePropertyAsInt("Ice.Warn.AMICallback") > 0)
 332        {
 333            instance_.initializationData().logger.warning("exception raised by AMI callback:\n" + ex);
 334        }
 335    }
 336
 337    //
 338    // This virtual method is necessary for the communicator flush
 339    // batch requests implementation.
 340    //
 341    protected virtual Ice.Instrumentation.InvocationObserver getObserver() => observer_;
 342
 343    public bool sentSynchronously() => sentSynchronously_;
 344
 345    protected Instance instance_;
 346    protected Ice.Connection cachedConnection_;
 347    protected bool sentSynchronously_;
 348    protected bool synchronous_;
 349    protected int state_;
 350
 351    protected Ice.Instrumentation.InvocationObserver observer_;
 352    protected Ice.Instrumentation.ChildInvocationObserver childObserver_;
 353
 354    protected Ice.OutputStream os_;
 355    protected Ice.InputStream is_;
 356
 357    protected readonly object mutex_ = new();
 358
 359    private bool _doneInSent;
 360    private bool _alreadySent;
 361    private Ice.Exception _ex;
 362    private Ice.LocalException _cancellationException;
 363    private CancellationHandler _cancellationHandler;
 364    private readonly OutgoingAsyncCompletionCallback _completionCallback;
 365
 366    protected const int StateOK = 0x1;
 367    protected const int StateDone = 0x2;
 368    protected const int StateSent = 0x4;
 369    protected const int StateEndCalled = 0x8;
 370    protected const int StateCachedBuffers = 0x10;
 371
 372    public const int AsyncStatusQueued = 0;
 373    public const int AsyncStatusSent = 1;
 374    public const int AsyncStatusInvokeSentCallback = 2;
 375}
 376
 377//
 378// Base class for proxy based invocations. This class handles the
 379// retry for proxy invocations. It also ensures the child observer is
 380// correct notified of failures and make sure the retry task is
 381// correctly canceled when the invocation completes.
 382//
 383public abstract class ProxyOutgoingAsyncBase : OutgoingAsyncBase, TimerTask
 384{
 385    public abstract int invokeRemote(Ice.ConnectionI connection, bool compress, bool response);
 386
 387    public abstract int invokeCollocated(CollocatedRequestHandler handler);
 388
 389    public override bool exception(Ice.Exception ex)
 390    {
 391        if (childObserver_ != null)
 392        {
 393            childObserver_.failed(ex.ice_id());
 394            childObserver_.detach();
 395            childObserver_ = null;
 396        }
 397
 398        cachedConnection_ = null;
 399
 400        //
 401        // NOTE: at this point, synchronization isn't needed, no other threads should be
 402        // calling on the callback.
 403        //
 404        try
 405        {
 406            //
 407            // It's important to let the retry queue do the retry even if
 408            // the retry interval is 0. This method can be called with the
 409            // connection locked so we can't just retry here.
 410            //
 411            instance_.retryQueue().add(this, handleRetryAfterException(ex));
 412            return false;
 413        }
 414        catch (Ice.Exception retryEx)
 415        {
 416            return exceptionImpl(retryEx); // No retries, we're done
 417        }
 418    }
 419
 420    public void retryException()
 421    {
 422        try
 423        {
 424            // It's important to let the retry queue do the retry. This is
 425            // called from the connect request handler and the retry might
 426            // require could end up waiting for the flush of the
 427            // connection to be done.
 428
 429            proxy_.iceGetRequestHandlerCache().clearCachedRequestHandler(handler_);
 430            instance_.retryQueue().add(this, 0);
 431        }
 432        catch (Ice.Exception ex)
 433        {
 434            if (exception(ex))
 435            {
 436                invokeExceptionAsync();
 437            }
 438        }
 439    }
 440
 441    public void retry() => invokeImpl(false);
 442
 443    public void abort(Ice.Exception ex)
 444    {
 445        Debug.Assert(childObserver_ == null);
 446        if (exceptionImpl(ex))
 447        {
 448            invokeExceptionAsync();
 449        }
 450        else if (ex is Ice.CommunicatorDestroyedException)
 451        {
 452            //
 453            // If it's a communicator destroyed exception, swallow
 454            // it but instead notify the user thread. Even if no callback
 455            // was provided.
 456            //
 457            throw ex;
 458        }
 459    }
 460
 461    protected ProxyOutgoingAsyncBase(
 462        Ice.ObjectPrxHelperBase prx,
 463        OutgoingAsyncCompletionCallback completionCallback,
 464        Ice.OutputStream os = null,
 465        Ice.InputStream iss = null)
 466        : base(prx.iceReference().getInstance(), completionCallback, os, iss)
 467    {
 468        proxy_ = prx;
 469        mode_ = Ice.OperationMode.Normal;
 470        _cnt = 0;
 471        _sent = false;
 472    }
 473
 474    protected void invokeImpl(bool userThread)
 475    {
 476        try
 477        {
 478            if (userThread)
 479            {
 480                TimeSpan invocationTimeout = proxy_.iceReference().getInvocationTimeout();
 481                if (invocationTimeout > TimeSpan.Zero)
 482                {
 483                    instance_.timer().schedule(this, (long)invocationTimeout.TotalMilliseconds);
 484                }
 485            }
 486            else
 487            {
 488                observer_?.retried();
 489            }
 490
 491            while (true)
 492            {
 493                try
 494                {
 495                    _sent = false;
 496                    handler_ = proxy_.iceGetRequestHandlerCache().requestHandler;
 497                    int status = handler_.sendAsyncRequest(this);
 498                    if ((status & AsyncStatusSent) != 0)
 499                    {
 500                        if (userThread)
 501                        {
 502                            sentSynchronously_ = true;
 503                            if ((status & AsyncStatusInvokeSentCallback) != 0)
 504                            {
 505                                invokeSent(); // Call the sent callback from the user thread.
 506                            }
 507                        }
 508                        else
 509                        {
 510                            if ((status & AsyncStatusInvokeSentCallback) != 0)
 511                            {
 512                                invokeSentAsync(); // Call the sent callback from a client thread pool thread.
 513                            }
 514                        }
 515                    }
 516                    return; // We're done!
 517                }
 518                catch (RetryException)
 519                {
 520                    // Clear request handler and always retry.
 521                    proxy_.iceGetRequestHandlerCache().clearCachedRequestHandler(handler_);
 522                }
 523                catch (Ice.Exception ex)
 524                {
 525                    if (childObserver_ != null)
 526                    {
 527                        childObserver_.failed(ex.ice_id());
 528                        childObserver_.detach();
 529                        childObserver_ = null;
 530                    }
 531                    int interval = handleRetryAfterException(ex);
 532                    if (interval > 0)
 533                    {
 534                        instance_.retryQueue().add(this, interval);
 535                        return;
 536                    }
 537                    else
 538                    {
 539                        observer_?.retried();
 540                    }
 541                }
 542            }
 543        }
 544        catch (Ice.Exception ex)
 545        {
 546            // If called from the user thread we re-throw, the exception will caught by the caller and handled using
 547            // abort.
 548            if (userThread)
 549            {
 550                throw;
 551            }
 552            else if (exceptionImpl(ex)) // No retries, we're done
 553            {
 554                invokeExceptionAsync();
 555            }
 556        }
 557    }
 558
 559    protected override bool sentImpl(bool done)
 560    {
 561        _sent = true;
 562        if (done)
 563        {
 564            if (proxy_.iceReference().getInvocationTimeout() > TimeSpan.Zero)
 565            {
 566                instance_.timer().cancel(this);
 567            }
 568        }
 569        return base.sentImpl(done);
 570    }
 571
 572    protected override bool exceptionImpl(Ice.Exception ex)
 573    {
 574        if (proxy_.iceReference().getInvocationTimeout() > TimeSpan.Zero)
 575        {
 576            instance_.timer().cancel(this);
 577        }
 578        return base.exceptionImpl(ex);
 579    }
 580
 581    protected override bool responseImpl(bool userThread, bool ok, bool invoke)
 582    {
 583        if (proxy_.iceReference().getInvocationTimeout() > TimeSpan.Zero)
 584        {
 585            instance_.timer().cancel(this);
 586        }
 587        return base.responseImpl(userThread, ok, invoke);
 588    }
 589
 590    public void runTimerTask() => cancel(new Ice.InvocationTimeoutException());
 591
 592    private int handleRetryAfterException(Ice.Exception ex)
 593    {
 594        // Clear the request handler
 595        proxy_.iceGetRequestHandlerCache().clearCachedRequestHandler(handler_);
 596
 597        // We only retry local exception.
 598        //
 599        // A CloseConnectionException indicates graceful server shutdown, and is therefore
 600        // always repeatable without violating "at-most-once". That's because by sending a
 601        // close connection message, the server guarantees that all outstanding requests
 602        // can safely be repeated.
 603        //
 604        // An ObjectNotExistException can always be retried as well without violating
 605        // "at-most-once" (see the implementation of the checkRetryAfterException method
 606        // below for the reasons why it can be useful).
 607        //
 608        // If the request didn't get sent or if it's non-mutating or idempotent it can
 609        // also always be retried if the retry count isn't reached.
 610        bool shouldRetry = ex is LocalException && (!_sent ||
 611            mode_ is not OperationMode.Normal ||
 612            ex is CloseConnectionException ||
 613            ex is ObjectNotExistException);
 614
 615        if (shouldRetry)
 616        {
 617            try
 618            {
 619                return checkRetryAfterException((LocalException)ex);
 620            }
 621            catch (CommunicatorDestroyedException)
 622            {
 623                throw ex; // The communicator is already destroyed, so we cannot retry.
 624            }
 625        }
 626        else
 627        {
 628            throw ex; // Retry could break at-most-once semantics, don't retry.
 629        }
 630    }
 631
 632    private int checkRetryAfterException(Ice.LocalException ex)
 633    {
 634        Reference @ref = proxy_.iceReference();
 635        Instance instance = @ref.getInstance();
 636
 637        TraceLevels traceLevels = instance.traceLevels();
 638        Ice.Logger logger = instance.initializationData().logger!;
 639
 640        // We don't retry batch requests because the exception might have caused
 641        // the all the requests batched with the connection to be aborted and we
 642        // want the application to be notified.
 643        if (@ref.getMode() == Reference.Mode.ModeBatchOneway || @ref.getMode() == Reference.Mode.ModeBatchDatagram)
 644        {
 645            throw ex;
 646        }
 647
 648        // If it's a fixed proxy, retrying isn't useful as the proxy is tied to
 649        // the connection and the request will fail with the exception.
 650        if (@ref is FixedReference)
 651        {
 652            throw ex;
 653        }
 654
 655        var one = ex as Ice.ObjectNotExistException;
 656        if (one is not null)
 657        {
 658            if (@ref.getRouterInfo() != null && one.operation == "ice_add_proxy")
 659            {
 660                // If we have a router, an ObjectNotExistException with an
 661                // operation name "ice_add_proxy" indicates to the client
 662                // that the router isn't aware of the proxy (for example,
 663                // because it was evicted by the router). In this case, we
 664                // must *always* retry, so that the missing proxy is added
 665                // to the router.
 666
 667                @ref.getRouterInfo().clearCache(@ref);
 668
 669                if (traceLevels.retry >= 1)
 670                {
 671                    string s = "retrying operation call to add proxy to router\n" + ex;
 672                    logger.trace(traceLevels.retryCat, s);
 673                }
 674                return 0; // We must always retry, so we don't look at the retry count.
 675            }
 676            else if (@ref.isIndirect())
 677            {
 678                // We retry ObjectNotExistException if the reference is indirect.
 679
 680                if (@ref.isWellKnown())
 681                {
 682                    LocatorInfo li = @ref.getLocatorInfo();
 683                    li?.clearCache(@ref);
 684                }
 685            }
 686            else
 687            {
 688                // For all other cases, we don't retry ObjectNotExistException.
 689                throw ex;
 690            }
 691        }
 692        else if (ex is Ice.RequestFailedException)
 693        {
 694            throw ex;
 695        }
 696
 697        // There is no point in retrying an operation that resulted in a
 698        // MarshalException. This must have been raised locally (because if
 699        // it happened in a server it would result in an UnknownLocalException
 700        // instead), which means there was a problem in this process that will
 701        // not change if we try again.
 702        //
 703        // A likely cause for a MarshalException is exceeding the
 704        // maximum message size. For example, a client can attempt to send a
 705        // message that exceeds the maximum memory size, or accumulate enough
 706        // batch requests without flushing that the maximum size is reached.
 707        //
 708        // This latter case is especially problematic, because if we were to
 709        // retry a batch request after a MarshalException, we would in fact
 710        // silently discard the accumulated requests and allow new batch
 711        // requests to accumulate. If the subsequent batched requests do not
 712        // exceed the maximum message size, it appears to the client that all
 713        // of the batched requests were accepted, when in reality only the
 714        // last few are actually sent.
 715        if (ex is Ice.MarshalException)
 716        {
 717            throw ex;
 718        }
 719
 720        // Don't retry if the communicator is destroyed, object adapter is deactivated,
 721        // or connection is closed by the application.
 722        if (ex is CommunicatorDestroyedException ||
 723           ex is ObjectAdapterDeactivatedException ||
 724           ex is ObjectAdapterDestroyedException ||
 725           (ex is ConnectionAbortedException connectionAbortedException &&
 726            connectionAbortedException.closedByApplication) ||
 727           (ex is ConnectionClosedException connectionClosedException &&
 728            connectionClosedException.closedByApplication))
 729        {
 730            throw ex;
 731        }
 732
 733        // Don't retry invocation timeouts.
 734        if (ex is Ice.InvocationTimeoutException || ex is Ice.InvocationCanceledException)
 735        {
 736            throw ex;
 737        }
 738
 739        ++_cnt;
 740        Debug.Assert(_cnt > 0);
 741
 742        int[] retryIntervals = instance.retryIntervals;
 743
 744        int interval;
 745        if (_cnt == (retryIntervals.Length + 1) && ex is Ice.CloseConnectionException)
 746        {
 747            // A close connection exception is always retried at least once, even if the retry
 748            // limit is reached.
 749            interval = 0;
 750        }
 751        else if (_cnt > retryIntervals.Length)
 752        {
 753            if (traceLevels.retry >= 1)
 754            {
 755                string s = "cannot retry operation call because retry limit has been exceeded\n" + ex;
 756                logger.trace(traceLevels.retryCat, s);
 757            }
 758            throw ex;
 759        }
 760        else
 761        {
 762            interval = retryIntervals[_cnt - 1];
 763        }
 764
 765        if (traceLevels.retry >= 1)
 766        {
 767            string s = "retrying operation call";
 768            if (interval > 0)
 769            {
 770                s += " in " + interval + "ms";
 771            }
 772            s += " because of exception\n" + ex;
 773            logger.trace(traceLevels.retryCat, s);
 774        }
 775
 776        return interval;
 777    }
 778
 779    protected readonly Ice.ObjectPrxHelperBase proxy_;
 780    protected RequestHandler handler_;
 781    protected Ice.OperationMode mode_;
 782
 783    private int _cnt;
 784    private bool _sent;
 785}
 786
 787//
 788// Class for handling Slice operation invocations
 789//
 790public class OutgoingAsync : ProxyOutgoingAsyncBase
 791{
 792    public OutgoingAsync(
 793        Ice.ObjectPrxHelperBase prx,
 794        OutgoingAsyncCompletionCallback completionCallback,
 795        Ice.OutputStream os = null,
 796        Ice.InputStream iss = null)
 797        : base(prx, completionCallback, os, iss)
 798    {
 799        encoding_ = Protocol.getCompatibleEncoding(proxy_.iceReference().getEncoding());
 800        synchronous_ = false;
 801    }
 802
 803    public void prepare(string operation, Ice.OperationMode mode, Dictionary<string, string> context)
 804    {
 805        if (proxy_.iceReference().getProtocol().major != Ice.Util.currentProtocol.major)
 806        {
 807            throw new FeatureNotSupportedException(
 808                $"Cannot send request using protocol version {proxy_.iceReference().getProtocol()}.");
 809        }
 810
 811        mode_ = mode;
 812
 813        observer_ = ObserverHelper.get(proxy_, operation, context);
 814
 815        if (proxy_.iceReference().isBatch)
 816        {
 817            proxy_.iceReference().batchRequestQueue.prepareBatchRequest(os_);
 818        }
 819        else
 820        {
 821            os_.writeBlob(Protocol.requestHdr);
 822        }
 823
 824        Reference rf = proxy_.iceReference();
 825
 826        Identity.ice_write(os_, rf.getIdentity());
 827
 828        //
 829        // For compatibility with the old FacetPath.
 830        //
 831        string facet = rf.getFacet();
 832        if (facet == null || facet.Length == 0)
 833        {
 834            os_.writeStringSeq(null);
 835        }
 836        else
 837        {
 838            string[] facetPath = { facet };
 839            os_.writeStringSeq(facetPath);
 840        }
 841
 842        os_.writeString(operation);
 843
 844        os_.writeByte((byte)mode);
 845
 846        if (context != null)
 847        {
 848            //
 849            // Explicit context
 850            //
 851            Ice.ContextHelper.write(os_, context);
 852        }
 853        else
 854        {
 855            //
 856            // Implicit context
 857            //
 858            Ice.ImplicitContextI implicitContext = rf.getInstance().getImplicitContext();
 859            Dictionary<string, string> prxContext = rf.getContext();
 860
 861            if (implicitContext == null)
 862            {
 863                Ice.ContextHelper.write(os_, prxContext);
 864            }
 865            else
 866            {
 867                implicitContext.write(prxContext, os_);
 868            }
 869        }
 870    }
 871
 872    public override bool sent() => sentImpl(!proxy_.ice_isTwoway()); // done = true if it's not a two-way proxy
 873
 874    public override bool response()
 875    {
 876        //
 877        // NOTE: this method is called from ConnectionI.parseMessage
 878        // with the connection locked. Therefore, it must not invoke
 879        // any user callbacks.
 880        //
 881        Debug.Assert(proxy_.ice_isTwoway()); // Can only be called for twoways.
 882
 883        if (childObserver_ != null)
 884        {
 885            childObserver_.reply(is_.size() - Protocol.headerSize - 4);
 886            childObserver_.detach();
 887            childObserver_ = null;
 888        }
 889
 890        try
 891        {
 892            // We can't (shouldn't) use the generated code to unmarshal a possibly unknown reply status.
 893            var replyStatus = (ReplyStatus)is_.readByte();
 894
 895            switch (replyStatus)
 896            {
 897                case ReplyStatus.Ok:
 898                    break;
 899
 900                case ReplyStatus.UserException:
 901                    observer_?.userException();
 902                    break;
 903
 904                case ReplyStatus.ObjectNotExist:
 905                case ReplyStatus.FacetNotExist:
 906                case ReplyStatus.OperationNotExist:
 907                {
 908                    var ident = new Ice.Identity(is_);
 909
 910                    //
 911                    // For compatibility with the old FacetPath.
 912                    //
 913                    string[] facetPath = is_.readStringSeq();
 914                    string facet;
 915                    if (facetPath.Length > 0)
 916                    {
 917                        if (facetPath.Length > 1)
 918                        {
 919                            throw new MarshalException(
 920                                $"Received invalid facet path with {facetPath.Length} elements.");
 921                        }
 922                        facet = facetPath[0];
 923                    }
 924                    else
 925                    {
 926                        facet = "";
 927                    }
 928
 929                    string operation = is_.readString();
 930                    throw replyStatus switch
 931                    {
 932                        ReplyStatus.ObjectNotExist => new ObjectNotExistException(ident, facet, operation),
 933                        ReplyStatus.FacetNotExist => new FacetNotExistException(ident, facet, operation),
 934                        _ => new OperationNotExistException(ident, facet, operation)
 935                    };
 936                }
 937
 938                default:
 939                {
 940                    string message = is_.readString();
 941                    throw replyStatus switch
 942                    {
 943                        ReplyStatus.UnknownException => new UnknownException(message),
 944                        ReplyStatus.UnknownLocalException => new UnknownLocalException(message),
 945                        ReplyStatus.UnknownUserException => new UnknownUserException(message),
 946                        _ => new DispatchException(replyStatus, message),
 947                    };
 948                }
 949            }
 950
 951            return responseImpl(false, replyStatus == ReplyStatus.Ok, true);
 952        }
 953        catch (Ice.Exception ex)
 954        {
 955            return exception(ex);
 956        }
 957    }
 958
 959    public override int invokeRemote(Ice.ConnectionI connection, bool compress, bool response)
 960    {
 961        cachedConnection_ = connection;
 962        return connection.sendAsyncRequest(this, compress, response, 0);
 963    }
 964
 965    public override int invokeCollocated(CollocatedRequestHandler handler)
 966    {
 967        // The stream cannot be cached if the proxy is not a twoway or there is an invocation timeout set.
 968        if (!proxy_.ice_isTwoway() || proxy_.iceReference().getInvocationTimeout() > TimeSpan.Zero)
 969        {
 970            // Disable caching by marking the streams as cached!
 971            state_ |= StateCachedBuffers;
 972        }
 973        return handler.invokeAsyncRequest(this, 0, synchronous_);
 974    }
 975
 976    public new void abort(Ice.Exception ex)
 977    {
 978        if (proxy_.iceReference().isBatch)
 979        {
 980            proxy_.iceReference().batchRequestQueue.abortBatchRequest(os_);
 981        }
 982
 983        base.abort(ex);
 984    }
 985
 986    protected void invoke(string operation, bool synchronous)
 987    {
 988        synchronous_ = synchronous;
 989        if (proxy_.iceReference().isBatch)
 990        {
 991            sentSynchronously_ = true;
 992            proxy_.iceReference().batchRequestQueue.finishBatchRequest(os_, proxy_, operation);
 993            responseImpl(true, true, false); // Don't call sent/completed callback for batch AMI requests
 994            return;
 995        }
 996
 997        // invokeImpl can throw
 998        invokeImpl(true); // userThread = true
 999    }
 1000
 1001    public void invoke(
 1002        string operation,
 1003        Ice.OperationMode mode,
 1004        Ice.FormatType? format,
 1005        Dictionary<string, string> context,
 1006        bool synchronous,
 1007        System.Action<Ice.OutputStream> write)
 1008    {
 1009        try
 1010        {
 1011            prepare(operation, mode, context);
 1012            if (write != null)
 1013            {
 1014                os_.startEncapsulation(encoding_, format);
 1015                write(os_);
 1016                os_.endEncapsulation();
 1017            }
 1018            else
 1019            {
 1020                os_.writeEmptyEncapsulation(encoding_);
 1021            }
 1022            invoke(operation, synchronous);
 1023        }
 1024        catch (Ice.Exception ex)
 1025        {
 1026            abort(ex);
 1027        }
 1028    }
 1029
 1030    public override void throwUserException()
 1031    {
 1032        try
 1033        {
 1034            is_.startEncapsulation();
 1035            is_.throwException();
 1036        }
 1037        catch (UserException ex)
 1038        {
 1039            is_.endEncapsulation();
 1040            userException_?.Invoke(ex);
 1041            throw UnknownUserException.fromTypeId(ex.ice_id());
 1042        }
 1043    }
 1044
 1045    public override void cacheMessageBuffers()
 1046    {
 1047        if (proxy_.iceReference().getInstance().cacheMessageBuffers() > 0)
 1048        {
 1049            lock (mutex_)
 1050            {
 1051                if ((state_ & StateCachedBuffers) > 0)
 1052                {
 1053                    return;
 1054                }
 1055                state_ |= StateCachedBuffers;
 1056            }
 1057
 1058            is_?.reset();
 1059            os_.reset();
 1060
 1061            proxy_.cacheMessageBuffers(is_, os_);
 1062
 1063            is_ = null;
 1064            os_ = null;
 1065        }
 1066    }
 1067
 1068    protected readonly Ice.EncodingVersion encoding_;
 1069    protected System.Action<Ice.UserException> userException_;
 1070}
 1071
 1072public class OutgoingAsyncT<T> : OutgoingAsync
 1073{
 1074    public OutgoingAsyncT(
 1075        Ice.ObjectPrxHelperBase prx,
 1076        OutgoingAsyncCompletionCallback completionCallback,
 1077        Ice.OutputStream os = null,
 1078        Ice.InputStream iss = null)
 1079        : base(prx, completionCallback, os, iss)
 1080    {
 1081    }
 1082
 1083    public void invoke(
 1084        string operation,
 1085        Ice.OperationMode mode,
 1086        Ice.FormatType? format,
 1087        Dictionary<string, string> context,
 1088        bool synchronous,
 1089        System.Action<Ice.OutputStream> write = null,
 1090        System.Action<Ice.UserException> userException = null,
 1091        System.Func<Ice.InputStream, T> read = null)
 1092    {
 1093        read_ = read;
 1094        userException_ = userException;
 1095        base.invoke(operation, mode, format, context, synchronous, write);
 1096    }
 1097
 1098    public T getResult(bool ok)
 1099    {
 1100        try
 1101        {
 1102            if (ok)
 1103            {
 1104                if (read_ == null)
 1105                {
 1106                    if (is_ == null || is_.isEmpty())
 1107                    {
 1108                        //
 1109                        // If there's no response (oneway, batch-oneway proxies), we just set the result
 1110                        // on completion without reading anything from the input stream. This is required for
 1111                        // batch invocations.
 1112                        //
 1113                    }
 1114                    else
 1115                    {
 1116                        is_.skipEmptyEncapsulation();
 1117                    }
 1118                    return default;
 1119                }
 1120                else
 1121                {
 1122                    is_.startEncapsulation();
 1123                    T r = read_(is_);
 1124                    is_.endEncapsulation();
 1125                    return r;
 1126                }
 1127            }
 1128            else
 1129            {
 1130                throwUserException();
 1131                return default; // make compiler happy
 1132            }
 1133        }
 1134        finally
 1135        {
 1136            cacheMessageBuffers();
 1137        }
 1138    }
 1139
 1140    protected System.Func<Ice.InputStream, T> read_;
 1141}
 1142
 1143//
 1144// Class for handling the proxy's begin_ice_flushBatchRequest request.
 1145//
 1146internal class ProxyFlushBatchAsync : ProxyOutgoingAsyncBase
 1147{
 1148    public ProxyFlushBatchAsync(Ice.ObjectPrxHelperBase prx, OutgoingAsyncCompletionCallback completionCallback)
 1149        : base(prx, completionCallback)
 1150    {
 1151    }
 1152
 1153    public override int invokeRemote(Ice.ConnectionI connection, bool compress, bool response)
 1154    {
 1155        if (_batchRequestNum == 0)
 1156        {
 1157            if (sent())
 1158            {
 1159                return AsyncStatusSent | AsyncStatusInvokeSentCallback;
 1160            }
 1161            else
 1162            {
 1163                return AsyncStatusSent;
 1164            }
 1165        }
 1166        cachedConnection_ = connection;
 1167        return connection.sendAsyncRequest(this, compress, false, _batchRequestNum);
 1168    }
 1169
 1170    public override int invokeCollocated(CollocatedRequestHandler handler)
 1171    {
 1172        if (_batchRequestNum == 0)
 1173        {
 1174            if (sent())
 1175            {
 1176                return AsyncStatusSent | AsyncStatusInvokeSentCallback;
 1177            }
 1178            else
 1179            {
 1180                return AsyncStatusSent;
 1181            }
 1182        }
 1183        return handler.invokeAsyncRequest(this, _batchRequestNum, false);
 1184    }
 1185
 1186    public void invoke(string operation, bool synchronous)
 1187    {
 1188        if (proxy_.iceReference().getProtocol().major != Ice.Util.currentProtocol.major)
 1189        {
 1190            throw new FeatureNotSupportedException(
 1191                $"Cannot send request using protocol version {proxy_.iceReference().getProtocol()}.");
 1192        }
 1193        try
 1194        {
 1195            synchronous_ = synchronous;
 1196            observer_ = ObserverHelper.get(proxy_, operation, null);
 1197            // Not used for proxy flush batch requests.
 1198            _batchRequestNum = proxy_.iceReference().batchRequestQueue.swap(os_, out _);
 1199            invokeImpl(true); // userThread = true
 1200        }
 1201        catch (Ice.Exception ex)
 1202        {
 1203            abort(ex);
 1204        }
 1205    }
 1206
 1207    private int _batchRequestNum;
 1208}
 1209
 1210//
 1211// Class for handling the proxy's begin_ice_getConnection request.
 1212//
 1213internal class ProxyGetConnection : ProxyOutgoingAsyncBase
 1214{
 1215    public ProxyGetConnection(Ice.ObjectPrxHelperBase prx, OutgoingAsyncCompletionCallback completionCallback)
 1216        : base(prx, completionCallback)
 1217    {
 1218    }
 1219
 1220    public override int invokeRemote(Ice.ConnectionI connection, bool compress, bool response)
 1221    {
 1222        cachedConnection_ = connection;
 1223        if (responseImpl(false, true, true))
 1224        {
 1225            invokeResponseAsync();
 1226        }
 1227        return AsyncStatusSent;
 1228    }
 1229
 1230    public override int invokeCollocated(CollocatedRequestHandler handler)
 1231    {
 1232        if (responseImpl(false, true, true))
 1233        {
 1234            invokeResponseAsync();
 1235        }
 1236        return AsyncStatusSent;
 1237    }
 1238
 1239    public Ice.Connection getConnection() => cachedConnection_;
 1240
 1241    public void invoke(string operation, bool synchronous)
 1242    {
 1243        try
 1244        {
 1245            synchronous_ = synchronous;
 1246            observer_ = ObserverHelper.get(proxy_, operation, null);
 1247            invokeImpl(true); // userThread = true
 1248        }
 1249        catch (Ice.Exception ex)
 1250        {
 1251            abort(ex);
 1252        }
 1253    }
 1254}
 1255
 1256internal class ConnectionFlushBatchAsync : OutgoingAsyncBase
 1257{
 1258    public ConnectionFlushBatchAsync(
 1259        Ice.ConnectionI connection,
 1260        Instance instance,
 1261        OutgoingAsyncCompletionCallback completionCallback)
 11262        : base(instance, completionCallback) => _connection = connection;
 1263
 1264    public void invoke(string operation, Ice.CompressBatch compressBatch, bool synchronous)
 1265    {
 11266        synchronous_ = synchronous;
 11267        observer_ = ObserverHelper.get(instance_, operation);
 1268        try
 1269        {
 1270            int status;
 11271            int batchRequestNum = _connection.getBatchRequestQueue().swap(os_, out bool compress);
 11272            if (batchRequestNum == 0)
 1273            {
 11274                status = AsyncStatusSent;
 11275                if (sent())
 1276                {
 11277                    status |= AsyncStatusInvokeSentCallback;
 1278                }
 1279            }
 1280            else
 1281            {
 1282                bool comp;
 11283                if (compressBatch == Ice.CompressBatch.Yes)
 1284                {
 11285                    comp = true;
 1286                }
 11287                else if (compressBatch == Ice.CompressBatch.No)
 1288                {
 11289                    comp = false;
 1290                }
 1291                else
 1292                {
 11293                    comp = compress;
 1294                }
 11295                status = _connection.sendAsyncRequest(this, comp, false, batchRequestNum);
 1296            }
 1297
 11298            if ((status & AsyncStatusSent) != 0)
 1299            {
 11300                sentSynchronously_ = true;
 11301                if ((status & AsyncStatusInvokeSentCallback) != 0)
 1302                {
 11303                    invokeSent();
 1304                }
 1305            }
 11306        }
 11307        catch (RetryException ex)
 1308        {
 1309            try
 1310            {
 11311                throw ex.get();
 1312            }
 11313            catch (Ice.LocalException ee)
 1314            {
 11315                if (exception(ee))
 1316                {
 11317                    invokeExceptionAsync();
 1318                }
 11319            }
 11320        }
 01321        catch (Ice.Exception ex)
 1322        {
 01323            if (exception(ex))
 1324            {
 01325                invokeExceptionAsync();
 1326            }
 01327        }
 11328    }
 1329
 1330    private readonly Ice.ConnectionI _connection;
 1331}
 1332
 1333public class CommunicatorFlushBatchAsync : OutgoingAsyncBase
 1334{
 1335    private class FlushBatch : OutgoingAsyncBase
 1336    {
 1337        public FlushBatch(
 1338            CommunicatorFlushBatchAsync outAsync,
 1339            Instance instance,
 1340            Ice.Instrumentation.InvocationObserver observer)
 1341            : base(instance, null)
 1342        {
 1343            _outAsync = outAsync;
 1344            _observer = observer;
 1345        }
 1346
 1347        public override bool
 1348        sent()
 1349        {
 1350            if (childObserver_ != null)
 1351            {
 1352                childObserver_.detach();
 1353                childObserver_ = null;
 1354            }
 1355            _outAsync.check(false);
 1356            return false;
 1357        }
 1358
 1359        public override bool
 1360        exception(Ice.Exception ex)
 1361        {
 1362            if (childObserver_ != null)
 1363            {
 1364                childObserver_.failed(ex.ice_id());
 1365                childObserver_.detach();
 1366                childObserver_ = null;
 1367            }
 1368            _outAsync.check(false);
 1369            return false;
 1370        }
 1371
 1372        protected override Ice.Instrumentation.InvocationObserver
 1373        getObserver() => _observer;
 1374
 1375        private readonly CommunicatorFlushBatchAsync _outAsync;
 1376        private readonly Ice.Instrumentation.InvocationObserver _observer;
 1377    }
 1378
 1379    public CommunicatorFlushBatchAsync(Instance instance, OutgoingAsyncCompletionCallback callback)
 1380        : base(instance, callback) =>
 1381        //
 1382        // _useCount is initialized to 1 to prevent premature callbacks.
 1383        // The caller must invoke ready() after all flush requests have
 1384        // been initiated.
 1385        //
 1386        _useCount = 1;
 1387
 1388    internal void flushConnection(Ice.ConnectionI con, Ice.CompressBatch compressBatch)
 1389    {
 1390        lock (mutex_)
 1391        {
 1392            ++_useCount;
 1393        }
 1394
 1395        try
 1396        {
 1397            var flushBatch = new FlushBatch(this, instance_, observer_);
 1398            int batchRequestNum = con.getBatchRequestQueue().swap(flushBatch.getOs(), out bool compress);
 1399            if (batchRequestNum == 0)
 1400            {
 1401                flushBatch.sent();
 1402            }
 1403            else
 1404            {
 1405                bool comp;
 1406                if (compressBatch == Ice.CompressBatch.Yes)
 1407                {
 1408                    comp = true;
 1409                }
 1410                else if (compressBatch == Ice.CompressBatch.No)
 1411                {
 1412                    comp = false;
 1413                }
 1414                else
 1415                {
 1416                    comp = compress;
 1417                }
 1418                con.sendAsyncRequest(flushBatch, comp, false, batchRequestNum);
 1419            }
 1420        }
 1421        catch (Ice.LocalException)
 1422        {
 1423            check(false);
 1424            throw;
 1425        }
 1426    }
 1427
 1428    public void invoke(string operation, Ice.CompressBatch compressBatch, bool synchronous)
 1429    {
 1430        synchronous_ = synchronous;
 1431        observer_ = ObserverHelper.get(instance_, operation);
 1432        instance_.outgoingConnectionFactory().flushAsyncBatchRequests(compressBatch, this);
 1433        instance_.objectAdapterFactory().flushAsyncBatchRequests(compressBatch, this);
 1434        check(true);
 1435    }
 1436
 1437    public void check(bool userThread)
 1438    {
 1439        lock (mutex_)
 1440        {
 1441            Debug.Assert(_useCount > 0);
 1442            if (--_useCount > 0)
 1443            {
 1444                return;
 1445            }
 1446        }
 1447
 1448        if (sentImpl(true))
 1449        {
 1450            if (userThread)
 1451            {
 1452                sentSynchronously_ = true;
 1453                invokeSent();
 1454            }
 1455            else
 1456            {
 1457                invokeSentAsync();
 1458            }
 1459        }
 1460    }
 1461
 1462    private int _useCount;
 1463}
 1464
 1465public abstract class TaskCompletionCallback<T> : TaskCompletionSource<T>, OutgoingAsyncCompletionCallback
 1466{
 1467    protected TaskCompletionCallback(System.IProgress<bool> progress, CancellationToken cancellationToken)
 1468        : base(TaskCreationOptions.RunContinuationsAsynchronously)
 1469    {
 1470        progress_ = progress;
 1471        _cancellationToken = cancellationToken;
 1472    }
 1473
 1474    public void init(OutgoingAsyncBase og)
 1475    {
 1476        if (_cancellationToken.CanBeCanceled)
 1477        {
 1478            _cancellationToken.Register(og.cancel);
 1479        }
 1480    }
 1481
 1482    public bool handleSent(bool done, bool alreadySent, OutgoingAsyncBase og)
 1483    {
 1484        if (done && og.isSynchronous())
 1485        {
 1486            Debug.Assert(progress_ == null);
 1487            handleInvokeSent(false, done, alreadySent, og);
 1488            return false;
 1489        }
 1490        return done || (progress_ != null && !alreadySent); // Invoke the sent callback only if not already invoked.
 1491    }
 1492
 1493    public bool handleException(Ice.Exception ex, OutgoingAsyncBase og)
 1494    {
 1495        //
 1496        // If this is a synchronous call, we can notify the task from this thread to avoid
 1497        // the thread context switch. We know there aren't any continuations setup with the
 1498        // task.
 1499        //
 1500        if (og.isSynchronous())
 1501        {
 1502            handleInvokeException(ex, og);
 1503            return false;
 1504        }
 1505        else
 1506        {
 1507            return true;
 1508        }
 1509    }
 1510
 1511    public bool handleResponse(bool userThread, bool ok, OutgoingAsyncBase og)
 1512    {
 1513        //
 1514        // If called from the user thread (only the case for batch requests) or if this
 1515        // is a synchronous call, we can notify the task from this thread to avoid the
 1516        // thread context switch. We know there aren't any continuations setup with the
 1517        // task.
 1518        //
 1519        if (userThread || og.isSynchronous())
 1520        {
 1521            handleInvokeResponse(ok, og);
 1522            return false;
 1523        }
 1524        else
 1525        {
 1526            return true;
 1527        }
 1528    }
 1529
 1530    public virtual void handleInvokeSent(bool sentSynchronously, bool done, bool alreadySent, OutgoingAsyncBase og)
 1531    {
 1532        if (progress_ != null && !alreadySent)
 1533        {
 1534            progress_.Report(sentSynchronously);
 1535        }
 1536        if (done)
 1537        {
 1538            SetResult(default);
 1539        }
 1540    }
 1541
 1542    public void handleInvokeException(Ice.Exception ex, OutgoingAsyncBase og) => SetException(ex);
 1543
 1544    public abstract void handleInvokeResponse(bool ok, OutgoingAsyncBase og);
 1545
 1546    private readonly CancellationToken _cancellationToken;
 1547
 1548    protected readonly System.IProgress<bool> progress_;
 1549}
 1550
 1551public class OperationTaskCompletionCallback<T> : TaskCompletionCallback<T>
 1552{
 1553    public OperationTaskCompletionCallback(System.IProgress<bool> progress, CancellationToken cancellationToken)
 1554        : base(progress, cancellationToken)
 1555    {
 1556    }
 1557
 1558    public override void handleInvokeResponse(bool ok, OutgoingAsyncBase og) =>
 1559        SetResult(((OutgoingAsyncT<T>)og).getResult(ok));
 1560}
 1561
 1562public class FlushBatchTaskCompletionCallback : TaskCompletionCallback<object>
 1563{
 1564    public FlushBatchTaskCompletionCallback(
 1565        IProgress<bool> progress = null,
 1566        CancellationToken cancellationToken = default)
 1567        : base(progress, cancellationToken)
 1568    {
 1569    }
 1570
 1571    public override void handleInvokeResponse(bool ok, OutgoingAsyncBase og) => SetResult(null);
 1572}