< Summary

Information
Class: IceBox.ServiceManagerI
Assembly: iceboxnet
File(s): /_/csharp/src/iceboxnet/ServiceManagerI.cs
Tag: 91_21789722663
Line coverage
62%
Covered lines: 261
Uncovered lines: 159
Coverable lines: 420
Total lines: 1030
Line coverage: 62.1%
Branch coverage
61%
Covered branches: 109
Total branches: 178
Branch coverage: 61.2%
Method coverage
68%
Covered methods: 15
Total methods: 22
Method coverage: 68.1%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)66.67%6.05688.89%
Dispose()50%22100%
startService(...)62.5%16.751685.71%
stopService(...)64.29%14.711484.62%
isServiceRunning(...)0%2040%
addObserver(...)0%110100%
servicesStartedAsync()100%210%
shutdown(...)100%11100%
run()93.75%45.823276.19%
startService(...)59.38%89.533261.7%
stopAll()90%15.271062.5%
servicesStarted(...)75%4.13480%
servicesStartedAsync()100%210%
servicesStopped(...)75%4.13480%
servicesStoppedAsync()100%210%
removeObserver(...)0%620%
observerRemoved(...)0%2040%
.ctor(...)12.5%14.8852.63%
createServiceProperties(...)90%10.061091.67%
destroyServiceCommunicator(...)100%2.39253.85%
configureAdmin(...)35.71%23.421463.64%
removeAdminFacets(...)100%6.6445.45%

File(s)

/_/csharp/src/iceboxnet/ServiceManagerI.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Collections;
 4using System.Diagnostics;
 5
 6namespace IceBox;
 7
 8internal class ServiceManagerI : ServiceManagerDisp_, IDisposable
 9{
 110    public ServiceManagerI(Ice.Communicator communicator, string[] args)
 11    {
 112        _communicator = communicator;
 113        _logger = _communicator.getLogger();
 14
 115        Ice.Properties props = _communicator.getProperties();
 16
 117        if (props.getIceProperty("Ice.Admin.Enabled").Length == 0)
 18        {
 119            _adminEnabled = props.getIceProperty("Ice.Admin.Endpoints").Length > 0;
 20        }
 21        else
 22        {
 023            _adminEnabled = props.getIcePropertyAsInt("Ice.Admin.Enabled") > 0;
 24        }
 25
 126        if (_adminEnabled)
 27        {
 128            string[] facetFilter = props.getIcePropertyAsList("Ice.Admin.Facets");
 129            if (facetFilter.Length > 0)
 30            {
 031                _adminFacetFilter = new HashSet<string>(facetFilter);
 32            }
 33            else
 34            {
 135                _adminFacetFilter = new HashSet<string>();
 36            }
 37        }
 38
 139        _argv = args;
 140        _traceServiceObserver = _communicator.getProperties().getIcePropertyAsInt("IceBox.Trace.ServiceObserver");
 141    }
 42
 143    public void Dispose() => _sharedCommunicator?.Dispose();
 44
 45    public override void startService(string name, Ice.Current current)
 46    {
 147        var info = new ServiceInfo();
 148        lock (_mutex)
 49        {
 50            //
 51            // Search would be more efficient if services were contained in
 52            // a map, but order is required for shutdown.
 53            //
 54            int i;
 155            for (i = 0; i < _services.Count; ++i)
 56            {
 157                info = _services[i];
 158                if (info.name == name)
 59                {
 160                    if (_services[i].status != ServiceStatus.Stopped)
 61                    {
 062                        throw new AlreadyStartedException();
 63                    }
 164                    info.status = ServiceStatus.Starting;
 165                    _services[i] = info;
 166                    break;
 67                }
 68            }
 169            if (i == _services.Count)
 70            {
 071                throw new NoSuchServiceException();
 72            }
 173            _pendingStatusChanges = true;
 174        }
 75
 176        bool started = false;
 77        try
 78        {
 179            info.service.start(
 180                info.name,
 181                info.communicator ?? _sharedCommunicator,
 182                info.args);
 183            started = true;
 184        }
 085        catch (Exception e)
 86        {
 087            _logger.warning("ServiceManager: exception while starting service " + info.name + ":\n" + e.ToString());
 088        }
 89
 190        lock (_mutex)
 91        {
 92            int i;
 193            for (i = 0; i < _services.Count; ++i)
 94            {
 195                info = _services[i];
 196                if (info.name.Equals(name, StringComparison.Ordinal))
 97                {
 198                    if (started)
 99                    {
 1100                        info.status = ServiceStatus.Started;
 101
 1102                        var services = new List<string>
 1103                        {
 1104                            name
 1105                        };
 1106                        servicesStarted(services, _observers.Keys);
 107                    }
 108                    else
 109                    {
 0110                        info.status = ServiceStatus.Stopped;
 111                    }
 1112                    _services[i] = info;
 1113                    break;
 114                }
 115            }
 1116            _pendingStatusChanges = false;
 1117            Monitor.PulseAll(_mutex);
 1118        }
 1119    }
 120
 121    public override void stopService(string name, Ice.Current current)
 122    {
 1123        var info = new ServiceInfo();
 1124        lock (_mutex)
 125        {
 126            //
 127            // Search would be more efficient if services were contained in
 128            // a map, but order is required for shutdown.
 129            //
 130            int i;
 1131            for (i = 0; i < _services.Count; ++i)
 132            {
 1133                info = _services[i];
 1134                if (info.name.Equals(name, StringComparison.Ordinal))
 135                {
 1136                    if (info.status != ServiceStatus.Started)
 137                    {
 0138                        throw new AlreadyStoppedException();
 139                    }
 1140                    info.status = ServiceStatus.Stopping;
 1141                    _services[i] = info;
 1142                    break;
 143                }
 144            }
 1145            if (i == _services.Count)
 146            {
 0147                throw new NoSuchServiceException();
 148            }
 1149            _pendingStatusChanges = true;
 1150        }
 151
 1152        bool stopped = false;
 153        try
 154        {
 1155            info.service.stop();
 1156            stopped = true;
 1157        }
 0158        catch (Exception e)
 159        {
 0160            _logger.warning("ServiceManager: exception while stopping service " + info.name + "\n" + e.ToString());
 0161        }
 162
 1163        lock (_mutex)
 164        {
 165            int i;
 1166            for (i = 0; i < _services.Count; ++i)
 167            {
 1168                info = _services[i];
 1169                if (info.name == name)
 170                {
 1171                    if (stopped)
 172                    {
 1173                        info.status = ServiceStatus.Stopped;
 174
 1175                        var services = new List<string>
 1176                        {
 1177                            name
 1178                        };
 1179                        servicesStopped(services, _observers.Keys);
 180                    }
 181                    else
 182                    {
 0183                        info.status = ServiceStatus.Started;
 184                    }
 1185                    _services[i] = info;
 1186                    break;
 187                }
 188            }
 1189            _pendingStatusChanges = false;
 1190            Monitor.PulseAll(_mutex);
 1191        }
 1192    }
 193
 194    public override bool isServiceRunning(string name, Ice.Current current)
 195    {
 0196        lock (_mutex)
 197        {
 0198            foreach (ServiceInfo info in _services)
 199            {
 0200                if (info.name == name)
 201                {
 0202                    return info.status == ServiceStatus.Started;
 203                }
 204            }
 205        }
 0206        throw new NoSuchServiceException();
 0207    }
 208
 209    public override void addObserver(ServiceObserverPrx observer, Ice.Current current)
 210    {
 0211        var activeServices = new List<string>();
 212
 213        //
 214        // Null observers and duplicate registrations are ignored
 215        //
 0216        lock (_mutex)
 217        {
 0218            if (observer != null)
 219            {
 220                try
 221                {
 0222                    _observers.Add(observer, true);
 0223                }
 0224                catch (ArgumentException)
 225                {
 0226                    return;
 227                }
 228
 0229                if (_traceServiceObserver >= 1)
 230                {
 0231                    _logger.trace(
 0232                        "IceBox.ServiceObserver",
 0233                        "Added service observer " + _communicator.proxyToString(observer));
 234                }
 235
 0236                foreach (ServiceInfo info in _services)
 237                {
 0238                    if (info.status == ServiceStatus.Started)
 239                    {
 0240                        activeServices.Add(info.name);
 241                    }
 242                }
 243            }
 0244        }
 245
 0246        if (activeServices.Count > 0)
 247        {
 0248            _ = servicesStartedAsync(observer, activeServices.ToArray());
 249        }
 250
 251        async Task servicesStartedAsync(ServiceObserverPrx observer, string[] services)
 252        {
 253            try
 254            {
 0255                await observer.servicesStartedAsync(services).ConfigureAwait(false);
 0256            }
 0257            catch (System.Exception ex)
 258            {
 0259                removeObserver(observer, ex);
 0260            }
 0261        }
 0262    }
 263
 1264    public override void shutdown(Ice.Current current) => _communicator.shutdown();
 265
 266    public int run()
 267    {
 268        try
 269        {
 1270            Ice.Properties properties = _communicator.getProperties();
 271
 272            //
 273            // Parse the property set with the prefix "IceBox.Service.". These
 274            // properties should have the following format:
 275            //
 276            // IceBox.Service.Foo=<assembly>:Package.Foo [args]
 277            //
 278            // We parse the service properties specified in IceBox.LoadOrder
 279            // first, then the ones from remaining services.
 280            //
 1281            string prefix = "IceBox.Service.";
 1282            Dictionary<string, string> services = properties.getPropertiesForPrefix(prefix);
 283
 1284            if (services.Count == 0)
 285            {
 0286                throw new FailureException("ServiceManager: configuration must include at least one IceBox service.");
 287            }
 288
 1289            string[] loadOrder = properties.getIcePropertyAsList("IceBox.LoadOrder");
 1290            var servicesInfo = new List<StartServiceInfo>();
 1291            for (int i = 0; i < loadOrder.Length; ++i)
 292            {
 1293                if (loadOrder[i].Length > 0)
 294                {
 1295                    string key = prefix + loadOrder[i];
 1296                    if (!services.TryGetValue(key, out string value))
 297                    {
 0298                        throw new FailureException($"ServiceManager: no service definition for '{loadOrder[i]}'.");
 299                    }
 1300                    servicesInfo.Add(new StartServiceInfo(loadOrder[i], value, _argv));
 1301                    services.Remove(key);
 302                }
 303            }
 1304            foreach (KeyValuePair<string, string> entry in services)
 305            {
 1306                string name = entry.Key[prefix.Length..];
 1307                string value = entry.Value;
 1308                servicesInfo.Add(new StartServiceInfo(name, value, _argv));
 309            }
 310
 311            //
 312            // Check if some services are using the shared communicator in which
 313            // case we create the shared communicator now with a property set that
 314            // is the union of all the service properties (from services that use
 315            // the shared communicator).
 316            //
 1317            if (properties.getPropertiesForPrefix("IceBox.UseSharedCommunicator.").Count > 0)
 318            {
 1319                var initData = new Ice.InitializationData();
 1320                initData.properties = createServiceProperties("SharedCommunicator");
 1321                foreach (StartServiceInfo service in servicesInfo)
 322                {
 1323                    if (properties.getIcePropertyAsInt("IceBox.UseSharedCommunicator." + service.name) <= 0)
 324                    {
 325                        continue;
 326                    }
 327
 328                    //
 329                    // Load the service properties using the shared communicator properties as
 330                    // the default properties.
 331                    //
 1332                    var svcProperties = new Ice.Properties(ref service.args, initData.properties);
 333
 334                    //
 335                    // Remove properties from the shared property set that a service explicitly clears.
 336                    //
 1337                    Dictionary<string, string> allProps = initData.properties.getPropertiesForPrefix("");
 1338                    foreach (string key in allProps.Keys)
 339                    {
 1340                        if (svcProperties.getProperty(key).Length == 0)
 341                        {
 1342                            initData.properties.setProperty(key, "");
 343                        }
 344                    }
 345
 346                    //
 347                    // Add the service properties to the shared communicator properties.
 348                    //
 1349                    foreach (KeyValuePair<string, string> entry in svcProperties.getPropertiesForPrefix(""))
 350                    {
 1351                        initData.properties.setProperty(entry.Key, entry.Value);
 352                    }
 353
 354                    //
 355                    // Parse <service>.* command line options (the Ice command line options
 356                    // were parsed by the Properties constructor called above).
 357                    //
 1358                    service.args = initData.properties.parseCommandLineOptions(service.name, service.args);
 359                }
 360
 1361                string facetNamePrefix = "IceBox.SharedCommunicator.";
 1362                bool addFacets = configureAdmin(initData.properties, facetNamePrefix);
 363
 1364                _sharedCommunicator = new Ice.Communicator(initData);
 365
 1366                if (addFacets)
 367                {
 368                    // Add all facets created on shared communicator to the IceBox communicator
 369                    // but renamed <prefix>.<facet-name>, except for the Process facet which is
 370                    // never added.
 1371                    foreach (KeyValuePair<string, Ice.Object> p in _sharedCommunicator.findAllAdminFacets())
 372                    {
 1373                        if (p.Key != "Process")
 374                        {
 1375                            _communicator.addAdminFacet(p.Value, facetNamePrefix + p.Key);
 376                        }
 377                    }
 378                }
 379            }
 380
 1381            foreach (StartServiceInfo s in servicesInfo)
 382            {
 1383                startService(s.name, s.entryPoint, s.args);
 384            }
 385
 386            //
 387            // Start Admin (if enabled).
 388            //
 1389            _communicator.addAdminFacet(this, "IceBox.ServiceManager");
 1390            _communicator.getAdmin();
 391
 392            //
 393            // We may want to notify external scripts that the services
 394            // have started and that IceBox is "ready".
 395            // This is done by defining the property IceBox.PrintServicesReady=bundleName
 396            //
 397            // bundleName is whatever you choose to call this set of
 398            // services. It will be echoed back as "bundleName ready".
 399            //
 400            // This must be done after start() has been invoked on the
 401            // services.
 402            //
 1403            string bundleName = properties.getIceProperty("IceBox.PrintServicesReady");
 1404            if (bundleName.Length > 0)
 405            {
 1406                Console.Out.WriteLine(bundleName + " ready");
 407            }
 408
 1409            _communicator.waitForShutdown();
 1410        }
 0411        catch (FailureException ex)
 412        {
 0413            _logger.error(ex.ToString());
 0414            return 1;
 415        }
 0416        catch (Ice.CommunicatorDestroyedException)
 417        {
 418            // Expected if the communicator is destroyed
 0419        }
 0420        catch (Ice.ObjectAdapterDeactivatedException)
 421        {
 422            // Expected if the communicator is shutdown
 0423        }
 0424        catch (Ice.ObjectAdapterDestroyedException)
 425        {
 426            // Expected if the communicator is destroyed
 0427        }
 0428        catch (Exception ex)
 429        {
 0430            _logger.error("ServiceManager: caught exception:\n" + ex.ToString());
 0431            return 1;
 432        }
 433        finally
 434        {
 435            //
 436            // Invoke stop() on the services.
 437            //
 1438            stopAll();
 1439        }
 440
 1441        return 0;
 0442    }
 443
 444    private void startService(string service, string entryPoint, string[] args)
 445    {
 1446        lock (_mutex)
 447        {
 448            //
 449            // Extract the assembly name and the class name.
 450            //
 1451            string err = "ServiceManager: unable to load service '" + entryPoint + "': ";
 1452            int sepPos = entryPoint.IndexOf(':', StringComparison.Ordinal);
 1453            if (sepPos != -1)
 454            {
 455                const string driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 1456                if (entryPoint.Length > 3 &&
 1457                   sepPos == 1 &&
 1458                   driveLetters.Contains(entryPoint[0], StringComparison.Ordinal) &&
 1459                   (entryPoint[2] == '\\' || entryPoint[2] == '/'))
 460                {
 0461                    sepPos = entryPoint.IndexOf(':', 3);
 462                }
 463            }
 1464            if (sepPos == -1)
 465            {
 0466                throw new FailureException($"{err}invalid entry point format.");
 467            }
 468
 1469            System.Reflection.Assembly serviceAssembly = null;
 1470            string assemblyName = entryPoint[..sepPos];
 1471            string className = entryPoint[(sepPos + 1)..];
 472
 473            try
 474            {
 475                //
 476                // First try to load the assembly using Assembly.Load, which will succeed
 477                // if a fully-qualified name is provided or if a partial name has been qualified
 478                // in configuration. If that fails, try Assembly.LoadFrom(), which will succeed
 479                // if a file name is configured or a partial name is configured and DEVPATH is used.
 480                //
 481                try
 482                {
 1483                    serviceAssembly = System.Reflection.Assembly.Load(assemblyName);
 0484                }
 1485                catch (Exception ex)
 486                {
 487                    try
 488                    {
 1489                        serviceAssembly = System.Reflection.Assembly.LoadFrom(assemblyName);
 1490                    }
 0491                    catch (Exception)
 492                    {
 0493                        throw ex;
 494                    }
 1495                }
 1496            }
 0497            catch (Exception ex)
 498            {
 0499                throw new FailureException($"{err}unable to load assembly: {assemblyName}", ex);
 500            }
 501
 502            //
 503            // Instantiate the class.
 504            //
 1505            Type c = null;
 506            try
 507            {
 1508                c = serviceAssembly.GetType(className, true);
 1509            }
 0510            catch (Exception ex)
 511            {
 0512                throw new FailureException($"{err}GetType failed for '{className}'.", ex);
 513            }
 514
 1515            var info = new ServiceInfo();
 1516            info.name = service;
 1517            info.status = ServiceStatus.Stopped;
 1518            info.args = args;
 519
 520            //
 521            // If IceBox.UseSharedCommunicator.<name> is defined, create a
 522            // communicator for the service. The communicator inherits
 523            // from the shared communicator properties. If it's not
 524            // defined, add the service properties to the shared
 525            // communicator property set.
 526            //
 527            Ice.Communicator communicator;
 1528            if (_communicator.getProperties().getIcePropertyAsInt("IceBox.UseSharedCommunicator." + service) > 0)
 529            {
 530                Debug.Assert(_sharedCommunicator != null);
 1531                communicator = _sharedCommunicator;
 532            }
 533            else
 534            {
 535                //
 536                // Create the service properties. We use the communicator properties as the default
 537                // properties if IceBox.InheritProperties is set.
 538                //
 1539                var initData = new Ice.InitializationData();
 1540                initData.properties = createServiceProperties(service);
 1541                if (info.args.Length > 0)
 542                {
 543                    //
 544                    // Create the service properties with the given service arguments. This should
 545                    // read the service config file if it's specified with --Ice.Config.
 546                    //
 1547                    initData.properties = new Ice.Properties(ref info.args, initData.properties);
 548
 549                    //
 550                    // Next, parse the service "<service>.*" command line options (the Ice command
 551                    // line options were parsed by the Properties constructor above)
 552                    //
 1553                    info.args = initData.properties.parseCommandLineOptions(service, info.args);
 554                }
 555
 556                //
 557                // If Admin is enabled on the IceBox communicator, for each service that does not set
 558                // Ice.Admin.Enabled, we set Ice.Admin.Enabled=1 to have this service create facets; then
 559                // we add these facets to the IceBox Admin object as IceBox.Service.<service>.<facet>.
 560                //
 1561                string serviceFacetNamePrefix = "IceBox.Service." + service + ".";
 1562                bool addFacets = configureAdmin(initData.properties, serviceFacetNamePrefix);
 563
 1564                info.communicator = new Ice.Communicator(initData);
 1565                communicator = info.communicator;
 566
 1567                if (addFacets)
 568                {
 569                    // Add all facets created on the service communicator to the IceBox communicator
 570                    // but renamed IceBox.Service.<service>.<facet-name>, except for the Process facet
 571                    // which is never added
 1572                    foreach (KeyValuePair<string, Ice.Object> p in communicator.findAllAdminFacets())
 573                    {
 1574                        if (p.Key != "Process")
 575                        {
 1576                            _communicator.addAdminFacet(p.Value, serviceFacetNamePrefix + p.Key);
 577                        }
 578                    }
 579                }
 580            }
 581
 582            try
 583            {
 584                //
 585                // Instantiate the service.
 586                //
 587                try
 588                {
 589                    //
 590                    // If the service class provides a constructor that accepts an Ice.Communicator argument,
 591                    // use that in preference to the default constructor.
 592                    //
 1593                    var parameterTypes = new Type[1];
 1594                    parameterTypes[0] = typeof(Ice.Communicator);
 1595                    System.Reflection.ConstructorInfo ci = c.GetConstructor(parameterTypes);
 1596                    if (ci != null)
 597                    {
 598                        try
 599                        {
 1600                            object[] parameters = new object[1];
 1601                            parameters[0] = _communicator;
 1602                            info.service = (Service)ci.Invoke(parameters);
 1603                        }
 0604                        catch (MethodAccessException ex)
 605                        {
 0606                            throw new FailureException(
 0607                                $"{err}unable to access service constructor {className}(Ice.Communicator).",
 0608                                ex);
 609                        }
 610                    }
 611                    else
 612                    {
 613                        //
 614                        // Fall back to the default constructor.
 615                        //
 616                        try
 617                        {
 1618                            info.service = (Service)Ice.Internal.AssemblyUtil.createInstance(c);
 1619                            if (info.service == null)
 620                            {
 0621                                throw new FailureException($"{err}no default constructor for '{className}'.");
 622                            }
 1623                        }
 0624                        catch (UnauthorizedAccessException ex)
 625                        {
 0626                            throw new FailureException(
 0627                                $"{err}unauthorized access to default service constructor for '{className}'.", ex);
 628                        }
 629                    }
 1630                }
 0631                catch (FailureException)
 632                {
 0633                    throw;
 634                }
 0635                catch (InvalidCastException ex)
 636                {
 0637                    throw new FailureException($"{err}service does not implement IceBox.Service.", ex);
 638                }
 0639                catch (System.Reflection.TargetInvocationException ex)
 640                {
 0641                    if (ex.InnerException is FailureException)
 642                    {
 0643                        throw ex.InnerException;
 644                    }
 645                    else
 646                    {
 0647                        throw new FailureException(
 0648                            $"{err}exception in service constructor for '{className}'.", ex.InnerException);
 649                    }
 650                }
 0651                catch (Exception ex)
 652                {
 0653                    throw new FailureException($"{err}exception in service constructor for '{className}'.", ex);
 654                }
 655
 656                try
 657                {
 1658                    info.service.start(service, communicator, info.args);
 1659                }
 0660                catch (FailureException)
 661                {
 0662                    throw;
 663                }
 0664                catch (Exception ex)
 665                {
 0666                    throw new FailureException($"{err}exception while starting service '{service}'.", ex);
 667                }
 668
 1669                info.status = ServiceStatus.Started;
 1670                _services.Add(info);
 1671            }
 0672            catch (Exception)
 673            {
 0674                if (info.communicator != null)
 675                {
 0676                    destroyServiceCommunicator(service, info.communicator);
 677                }
 0678                throw;
 679            }
 680        }
 1681    }
 682
 683    private void stopAll()
 684    {
 1685        lock (_mutex)
 686        {
 687            //
 688            // First wait for any active startService/stopService calls to complete.
 689            //
 1690            while (_pendingStatusChanges)
 691            {
 0692                Monitor.Wait(_mutex);
 693            }
 694
 695            //
 696            // For each service, we call stop on the service.
 697            // Services are stopped in the reverse order of the order they were started.
 698            //
 1699            _services.Reverse();
 1700            var stoppedServices = new List<string>();
 1701            foreach (ServiceInfo info in _services)
 702            {
 1703                if (info.status == ServiceStatus.Started)
 704                {
 705                    try
 706                    {
 1707                        info.service.stop();
 1708                        stoppedServices.Add(info.name);
 1709                    }
 0710                    catch (Exception e)
 711                    {
 0712                        _logger.warning(
 0713                            "IceBox.ServiceManager: exception while stopping service " +
 0714                                        info.name +
 0715                                        ":\n" +
 0716                                        e.ToString());
 0717                    }
 718                }
 719
 1720                if (info.communicator != null)
 721                {
 1722                    destroyServiceCommunicator(info.name, info.communicator);
 723                }
 724            }
 725
 1726            if (_sharedCommunicator != null)
 727            {
 1728                removeAdminFacets("IceBox.SharedCommunicator.");
 729
 730                try
 731                {
 1732                    _sharedCommunicator.destroy();
 1733                }
 0734                catch (Exception e)
 735                {
 0736                    _logger.warning(
 0737                        "ServiceManager: exception while destroying shared communicator:\n" + e.ToString());
 0738                }
 1739                _sharedCommunicator = null;
 740            }
 741
 1742            _services.Clear();
 1743            servicesStopped(stoppedServices, _observers.Keys);
 1744        }
 1745    }
 746
 747    private void servicesStarted(List<string> services, Dictionary<ServiceObserverPrx, bool>.KeyCollection observers)
 748    {
 749        //
 750        // Must be called with 'this' unlocked
 751        //
 752
 1753        if (services.Count > 0)
 754        {
 1755            string[] servicesArray = services.ToArray();
 756
 1757            foreach (ServiceObserverPrx observer in observers)
 758            {
 0759                _ = servicesStartedAsync(observer, servicesArray);
 760            }
 761        }
 762
 763        async Task servicesStartedAsync(ServiceObserverPrx observer, string[] services)
 764        {
 765            try
 766            {
 0767                await observer.servicesStartedAsync(services).ConfigureAwait(false);
 0768            }
 0769            catch (System.Exception ex)
 770            {
 0771                removeObserver(observer, ex);
 0772            }
 0773        }
 1774    }
 775
 776    private void servicesStopped(List<string> services, Dictionary<ServiceObserverPrx, bool>.KeyCollection observers)
 777    {
 778        //
 779        // Must be called with 'this' unlocked
 780        //
 781
 1782        if (services.Count > 0)
 783        {
 1784            string[] servicesArray = services.ToArray();
 785
 1786            foreach (ServiceObserverPrx observer in observers)
 787            {
 0788                _ = servicesStoppedAsync(observer, servicesArray);
 789            }
 790        }
 791
 792        async Task servicesStoppedAsync(ServiceObserverPrx observer, string[] services)
 793        {
 794            try
 795            {
 0796                await observer.servicesStoppedAsync(services).ConfigureAwait(false);
 0797            }
 0798            catch (System.Exception ex)
 799            {
 0800                removeObserver(observer, ex);
 0801            }
 0802        }
 1803    }
 804
 805    private void
 806    removeObserver(ServiceObserverPrx observer, System.Exception ex)
 807    {
 0808        lock (_mutex)
 809        {
 0810            if (_observers.Remove(observer))
 811            {
 0812                observerRemoved(observer, ex);
 813            }
 0814        }
 0815    }
 816
 817    private void observerRemoved(ServiceObserverPrx observer, Exception ex)
 818    {
 0819        if (_traceServiceObserver >= 1)
 820        {
 821            //
 822            // CommunicatorDestroyedException may occur during shutdown. The observer notification has
 823            // been sent, but the communicator was destroyed before the reply was received. We do not
 824            // log a message for this exception.
 825            //
 0826            if (!(ex is Ice.CommunicatorDestroyedException))
 827            {
 0828                _logger.trace(
 0829                    "IceBox.ServiceObserver",
 0830                    "Removed service observer " + _communicator.proxyToString(observer)
 0831                    + "\nafter catching " + ex.ToString());
 832            }
 833        }
 0834    }
 835
 836    private enum ServiceStatus
 837    {
 838        Stopping,
 839        Stopped,
 840        Starting,
 841        Started
 842    }
 843
 844    private struct ServiceInfo
 845    {
 846        public string name;
 847        public Service service;
 848        public Ice.Communicator communicator;
 849        public ServiceStatus status;
 850        public string[] args;
 851    }
 852
 853    private class StartServiceInfo
 854    {
 1855        public StartServiceInfo(string service, string value, string[] serverArgs)
 856        {
 857            //
 858            // Separate the entry point from the arguments.
 859            //
 1860            name = service;
 861
 862            try
 863            {
 1864                args = Ice.UtilInternal.Options.split(value);
 1865            }
 0866            catch (Ice.ParseException ex)
 867            {
 0868                throw new FailureException($"ServiceManager: invalid arguments for service '{name}'.", ex);
 869            }
 870
 871            Debug.Assert(args.Length > 0);
 872
 1873            entryPoint = args[0];
 874
 875            //
 876            // Shift the arguments.
 877            //
 1878            string[] tmp = new string[args.Length - 1];
 1879            Array.Copy(args, 1, tmp, 0, args.Length - 1);
 1880            args = tmp;
 881
 1882            if (serverArgs.Length > 0)
 883            {
 0884                var l = new ArrayList();
 0885                for (int j = 0; j < args.Length; j++)
 886                {
 0887                    l.Add(args[j]);
 888                }
 0889                for (int j = 0; j < serverArgs.Length; j++)
 890                {
 0891                    if (serverArgs[j].StartsWith("--" + service + ".", StringComparison.Ordinal))
 892                    {
 0893                        l.Add(serverArgs[j]);
 894                    }
 895                }
 0896                args = (string[])l.ToArray(typeof(string));
 897            }
 1898        }
 899
 900        public string name;
 901        public string entryPoint;
 902        public string[] args;
 903    }
 904
 905    private Ice.Properties createServiceProperties(string service)
 906    {
 1907        var properties = new Ice.Properties();
 1908        Ice.Properties communicatorProperties = _communicator.getProperties();
 1909        if (communicatorProperties.getIcePropertyAsInt("IceBox.InheritProperties") > 0)
 910        {
 911            // Inherit all except IceBox. and Ice.Admin. properties
 1912            foreach (KeyValuePair<string, string> property in communicatorProperties.getPropertiesForPrefix(""))
 913            {
 1914                if (!property.Key.StartsWith("IceBox.", StringComparison.Ordinal) &&
 1915                    !property.Key.StartsWith("Ice.Admin.", StringComparison.Ordinal))
 916                {
 1917                    properties.setProperty(property.Key, property.Value);
 918                }
 919            }
 920        }
 921
 1922        string programName = communicatorProperties.getIceProperty("Ice.ProgramName");
 1923        if (programName.Length == 0)
 924        {
 0925            properties.setProperty("Ice.ProgramName", service);
 926        }
 927        else
 928        {
 1929            properties.setProperty("Ice.ProgramName", programName + "-" + service);
 930        }
 1931        return properties;
 932    }
 933
 934    private void destroyServiceCommunicator(string service, Ice.Communicator communicator)
 935    {
 1936        if (communicator != null)
 937        {
 938            try
 939            {
 1940                communicator.shutdown();
 1941                communicator.waitForShutdown();
 1942            }
 0943            catch (Ice.CommunicatorDestroyedException)
 944            {
 945                //
 946                // Ignore, the service might have already destroyed
 947                // the communicator for its own reasons.
 948                //
 0949            }
 0950            catch (Exception e)
 951            {
 0952                _logger.warning("ServiceManager: exception while shutting down communicator for service "
 0953                                + service + "\n" + e.ToString());
 0954            }
 955
 1956            removeAdminFacets("IceBox.Service." + service + ".");
 1957            communicator.destroy();
 958        }
 1959    }
 960
 961    private bool configureAdmin(Ice.Properties properties, string prefix)
 962    {
 1963        if (_adminEnabled && properties.getIceProperty("Ice.Admin.Enabled").Length == 0)
 964        {
 1965            var facetNames = new List<string>();
 1966            foreach (string p in _adminFacetFilter)
 967            {
 0968                if (p.StartsWith(prefix, StringComparison.Ordinal))
 969                {
 0970                    facetNames.Add(p[prefix.Length..]);
 971                }
 972            }
 973
 1974            if (_adminFacetFilter.Count == 0 || facetNames.Count > 0)
 975            {
 1976                properties.setProperty("Ice.Admin.Enabled", "1");
 977
 1978                if (facetNames.Count > 0)
 979                {
 980                    // TODO: need String.Join with escape!
 0981                    properties.setProperty("Ice.Admin.Facets", string.Join(" ", facetNames.ToArray()));
 982                }
 1983                return true;
 984            }
 985        }
 0986        return false;
 987    }
 988
 989    private void removeAdminFacets(string prefix)
 990    {
 991        try
 992        {
 1993            foreach (string p in _communicator.findAllAdminFacets().Keys)
 994            {
 1995                if (p.StartsWith(prefix, StringComparison.Ordinal))
 996                {
 1997                    _communicator.removeAdminFacet(p);
 998                }
 999            }
 11000        }
 01001        catch (Ice.CommunicatorDestroyedException)
 1002        {
 1003            // Ignored
 01004        }
 01005        catch (Ice.ObjectAdapterDeactivatedException)
 1006        {
 1007            // Ignored
 01008        }
 01009        catch (Ice.ObjectAdapterDestroyedException)
 1010        {
 1011            // Ignored
 01012        }
 11013    }
 1014
 1015    private readonly Ice.Communicator _communicator;
 1016    private readonly bool _adminEnabled;
 1017    private readonly HashSet<string> _adminFacetFilter;
 1018    private Ice.Communicator _sharedCommunicator;
 1019
 1020#pragma warning disable CA2213
 1021    // This class does not own _logger.
 1022    private readonly Ice.Logger _logger;
 1023#pragma warning restore CA2213
 1024    private readonly string[] _argv; // Filtered server argument vector
 11025    private readonly List<ServiceInfo> _services = new();
 1026    private bool _pendingStatusChanges;
 11027    private readonly Dictionary<ServiceObserverPrx, bool> _observers = new();
 1028    private readonly int _traceServiceObserver;
 11029    private readonly object _mutex = new();
 1030}