< Summary

Information
Class: IceBox.ServiceManagerI
Assembly: iceboxnet
File(s): /_/csharp/src/iceboxnet/ServiceManagerI.cs
Tag: 99_23991109993
Line coverage
61%
Covered lines: 261
Uncovered lines: 162
Coverable lines: 423
Total lines: 1027
Line coverage: 61.7%
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%86.563262.37%
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                                throw new FailureException($"{err}no default constructor for '{className}'.");
 1620                        }
 0621                        catch (UnauthorizedAccessException ex)
 622                        {
 0623                            throw new FailureException(
 0624                                $"{err}unauthorized access to default service constructor for '{className}'.", ex);
 625                        }
 626                    }
 1627                }
 0628                catch (FailureException)
 629                {
 0630                    throw;
 631                }
 0632                catch (InvalidCastException ex)
 633                {
 0634                    throw new FailureException($"{err}service does not implement IceBox.Service.", ex);
 635                }
 0636                catch (System.Reflection.TargetInvocationException ex)
 637                {
 0638                    if (ex.InnerException is FailureException)
 639                    {
 0640                        throw ex.InnerException;
 641                    }
 642                    else
 643                    {
 0644                        throw new FailureException(
 0645                            $"{err}exception in service constructor for '{className}'.", ex.InnerException);
 646                    }
 647                }
 0648                catch (Exception ex)
 649                {
 0650                    throw new FailureException($"{err}exception in service constructor for '{className}'.", ex);
 651                }
 652
 653                try
 654                {
 1655                    info.service.start(service, communicator, info.args);
 1656                }
 0657                catch (FailureException)
 658                {
 0659                    throw;
 660                }
 0661                catch (Exception ex)
 662                {
 0663                    throw new FailureException($"{err}exception while starting service '{service}'.", ex);
 664                }
 665
 1666                info.status = ServiceStatus.Started;
 1667                _services.Add(info);
 1668            }
 0669            catch (Exception)
 670            {
 0671                if (info.communicator != null)
 672                {
 0673                    destroyServiceCommunicator(service, info.communicator);
 674                }
 0675                throw;
 676            }
 677        }
 1678    }
 679
 680    private void stopAll()
 681    {
 1682        lock (_mutex)
 683        {
 684            //
 685            // First wait for any active startService/stopService calls to complete.
 686            //
 1687            while (_pendingStatusChanges)
 688            {
 0689                Monitor.Wait(_mutex);
 690            }
 691
 692            //
 693            // For each service, we call stop on the service.
 694            // Services are stopped in the reverse order of the order they were started.
 695            //
 1696            _services.Reverse();
 1697            var stoppedServices = new List<string>();
 1698            foreach (ServiceInfo info in _services)
 699            {
 1700                if (info.status == ServiceStatus.Started)
 701                {
 702                    try
 703                    {
 1704                        info.service.stop();
 1705                        stoppedServices.Add(info.name);
 1706                    }
 0707                    catch (Exception e)
 708                    {
 0709                        _logger.warning(
 0710                            "IceBox.ServiceManager: exception while stopping service " +
 0711                                        info.name +
 0712                                        ":\n" +
 0713                                        e.ToString());
 0714                    }
 715                }
 716
 1717                if (info.communicator != null)
 718                {
 1719                    destroyServiceCommunicator(info.name, info.communicator);
 720                }
 721            }
 722
 1723            if (_sharedCommunicator != null)
 724            {
 1725                removeAdminFacets("IceBox.SharedCommunicator.");
 726
 727                try
 728                {
 1729                    _sharedCommunicator.destroy();
 1730                }
 0731                catch (Exception e)
 732                {
 0733                    _logger.warning(
 0734                        "ServiceManager: exception while destroying shared communicator:\n" + e.ToString());
 0735                }
 1736                _sharedCommunicator = null;
 737            }
 738
 1739            _services.Clear();
 1740            servicesStopped(stoppedServices, _observers.Keys);
 1741        }
 1742    }
 743
 744    private void servicesStarted(List<string> services, Dictionary<ServiceObserverPrx, bool>.KeyCollection observers)
 745    {
 1746        if (services.Count > 0)
 747        {
 1748            string[] servicesArray = services.ToArray();
 749
 1750            foreach (ServiceObserverPrx observer in observers)
 751            {
 0752                _ = servicesStartedAsync(observer, servicesArray);
 753            }
 754        }
 755
 756        async Task servicesStartedAsync(ServiceObserverPrx observer, string[] services)
 757        {
 758            try
 759            {
 0760                await observer.servicesStartedAsync(services).ConfigureAwait(false);
 0761            }
 0762            catch (Ice.CommunicatorDestroyedException)
 763            {
 764                // Expected during shutdown if the observer's communicator is destroyed.
 0765            }
 0766            catch (System.Exception ex)
 767            {
 0768                removeObserver(observer, ex);
 0769            }
 0770        }
 1771    }
 772
 773    private void servicesStopped(List<string> services, Dictionary<ServiceObserverPrx, bool>.KeyCollection observers)
 774    {
 1775        if (services.Count > 0)
 776        {
 1777            string[] servicesArray = services.ToArray();
 778
 1779            foreach (ServiceObserverPrx observer in observers)
 780            {
 0781                _ = servicesStoppedAsync(observer, servicesArray);
 782            }
 783        }
 784
 785        async Task servicesStoppedAsync(ServiceObserverPrx observer, string[] services)
 786        {
 787            try
 788            {
 0789                await observer.servicesStoppedAsync(services).ConfigureAwait(false);
 0790            }
 0791            catch (Ice.CommunicatorDestroyedException)
 792            {
 793                // Expected during shutdown if the observer's communicator is destroyed.
 0794            }
 0795            catch (System.Exception ex)
 796            {
 0797                removeObserver(observer, ex);
 0798            }
 0799        }
 1800    }
 801
 802    private void
 803    removeObserver(ServiceObserverPrx observer, System.Exception ex)
 804    {
 0805        lock (_mutex)
 806        {
 0807            if (_observers.Remove(observer))
 808            {
 0809                observerRemoved(observer, ex);
 810            }
 0811        }
 0812    }
 813
 814    private void observerRemoved(ServiceObserverPrx observer, Exception ex)
 815    {
 0816        if (_traceServiceObserver >= 1)
 817        {
 818            //
 819            // CommunicatorDestroyedException may occur during shutdown. The observer notification has
 820            // been sent, but the communicator was destroyed before the reply was received. We do not
 821            // log a message for this exception.
 822            //
 0823            if (!(ex is Ice.CommunicatorDestroyedException))
 824            {
 0825                _logger.trace(
 0826                    "IceBox.ServiceObserver",
 0827                    "Removed service observer " + _communicator.proxyToString(observer)
 0828                    + "\nafter catching " + ex.ToString());
 829            }
 830        }
 0831    }
 832
 833    private enum ServiceStatus
 834    {
 835        Stopping,
 836        Stopped,
 837        Starting,
 838        Started
 839    }
 840
 841    private struct ServiceInfo
 842    {
 843        public string name;
 844        public Service service;
 845        public Ice.Communicator communicator;
 846        public ServiceStatus status;
 847        public string[] args;
 848    }
 849
 850    private class StartServiceInfo
 851    {
 1852        public StartServiceInfo(string service, string value, string[] serverArgs)
 853        {
 854            //
 855            // Separate the entry point from the arguments.
 856            //
 1857            name = service;
 858
 859            try
 860            {
 1861                args = Ice.UtilInternal.Options.split(value);
 1862            }
 0863            catch (Ice.ParseException ex)
 864            {
 0865                throw new FailureException($"ServiceManager: invalid arguments for service '{name}'.", ex);
 866            }
 867
 868            Debug.Assert(args.Length > 0);
 869
 1870            entryPoint = args[0];
 871
 872            //
 873            // Shift the arguments.
 874            //
 1875            string[] tmp = new string[args.Length - 1];
 1876            Array.Copy(args, 1, tmp, 0, args.Length - 1);
 1877            args = tmp;
 878
 1879            if (serverArgs.Length > 0)
 880            {
 0881                var l = new ArrayList();
 0882                for (int j = 0; j < args.Length; j++)
 883                {
 0884                    l.Add(args[j]);
 885                }
 0886                for (int j = 0; j < serverArgs.Length; j++)
 887                {
 0888                    if (serverArgs[j].StartsWith("--" + service + ".", StringComparison.Ordinal))
 889                    {
 0890                        l.Add(serverArgs[j]);
 891                    }
 892                }
 0893                args = (string[])l.ToArray(typeof(string));
 894            }
 1895        }
 896
 897        public string name;
 898        public string entryPoint;
 899        public string[] args;
 900    }
 901
 902    private Ice.Properties createServiceProperties(string service)
 903    {
 1904        var properties = new Ice.Properties();
 1905        Ice.Properties communicatorProperties = _communicator.getProperties();
 1906        if (communicatorProperties.getIcePropertyAsInt("IceBox.InheritProperties") > 0)
 907        {
 908            // Inherit all except IceBox. and Ice.Admin. properties
 1909            foreach (KeyValuePair<string, string> property in communicatorProperties.getPropertiesForPrefix(""))
 910            {
 1911                if (!property.Key.StartsWith("IceBox.", StringComparison.Ordinal) &&
 1912                    !property.Key.StartsWith("Ice.Admin.", StringComparison.Ordinal))
 913                {
 1914                    properties.setProperty(property.Key, property.Value);
 915                }
 916            }
 917        }
 918
 1919        string programName = communicatorProperties.getIceProperty("Ice.ProgramName");
 1920        if (programName.Length == 0)
 921        {
 0922            properties.setProperty("Ice.ProgramName", service);
 923        }
 924        else
 925        {
 1926            properties.setProperty("Ice.ProgramName", programName + "-" + service);
 927        }
 1928        return properties;
 929    }
 930
 931    private void destroyServiceCommunicator(string service, Ice.Communicator communicator)
 932    {
 1933        if (communicator != null)
 934        {
 935            try
 936            {
 1937                communicator.shutdown();
 1938                communicator.waitForShutdown();
 1939            }
 0940            catch (Ice.CommunicatorDestroyedException)
 941            {
 942                //
 943                // Ignore, the service might have already destroyed
 944                // the communicator for its own reasons.
 945                //
 0946            }
 0947            catch (Exception e)
 948            {
 0949                _logger.warning("ServiceManager: exception while shutting down communicator for service "
 0950                                + service + "\n" + e.ToString());
 0951            }
 952
 1953            removeAdminFacets("IceBox.Service." + service + ".");
 1954            communicator.destroy();
 955        }
 1956    }
 957
 958    private bool configureAdmin(Ice.Properties properties, string prefix)
 959    {
 1960        if (_adminEnabled && properties.getIceProperty("Ice.Admin.Enabled").Length == 0)
 961        {
 1962            var facetNames = new List<string>();
 1963            foreach (string p in _adminFacetFilter)
 964            {
 0965                if (p.StartsWith(prefix, StringComparison.Ordinal))
 966                {
 0967                    facetNames.Add(p[prefix.Length..]);
 968                }
 969            }
 970
 1971            if (_adminFacetFilter.Count == 0 || facetNames.Count > 0)
 972            {
 1973                properties.setProperty("Ice.Admin.Enabled", "1");
 974
 1975                if (facetNames.Count > 0)
 976                {
 977                    // TODO: need String.Join with escape!
 0978                    properties.setProperty("Ice.Admin.Facets", string.Join(" ", facetNames.ToArray()));
 979                }
 1980                return true;
 981            }
 982        }
 0983        return false;
 984    }
 985
 986    private void removeAdminFacets(string prefix)
 987    {
 988        try
 989        {
 1990            foreach (string p in _communicator.findAllAdminFacets().Keys)
 991            {
 1992                if (p.StartsWith(prefix, StringComparison.Ordinal))
 993                {
 1994                    _communicator.removeAdminFacet(p);
 995                }
 996            }
 1997        }
 0998        catch (Ice.CommunicatorDestroyedException)
 999        {
 1000            // Ignored
 01001        }
 01002        catch (Ice.ObjectAdapterDeactivatedException)
 1003        {
 1004            // Ignored
 01005        }
 01006        catch (Ice.ObjectAdapterDestroyedException)
 1007        {
 1008            // Ignored
 01009        }
 11010    }
 1011
 1012    private readonly Ice.Communicator _communicator;
 1013    private readonly bool _adminEnabled;
 1014    private readonly HashSet<string> _adminFacetFilter;
 1015    private Ice.Communicator _sharedCommunicator;
 1016
 1017#pragma warning disable CA2213
 1018    // This class does not own _logger.
 1019    private readonly Ice.Logger _logger;
 1020#pragma warning restore CA2213
 1021    private readonly string[] _argv; // Filtered server argument vector
 11022    private readonly List<ServiceInfo> _services = new();
 1023    private bool _pendingStatusChanges;
 11024    private readonly Dictionary<ServiceObserverPrx, bool> _observers = new();
 1025    private readonly int _traceServiceObserver;
 11026    private readonly object _mutex = new();
 1027}