< Summary

Information
Class: IceBox.ServiceManagerI
Assembly: iceboxnet
File(s): /home/runner/work/ice/ice/csharp/src/iceboxnet/ServiceManagerI.cs
Tag: 71_18251537082
Line coverage
63%
Covered lines: 262
Uncovered lines: 153
Coverable lines: 415
Total lines: 1021
Line coverage: 63.1%
Branch coverage
63%
Covered branches: 110
Total branches: 174
Branch coverage: 63.2%
Method coverage
70%
Covered methods: 14
Total methods: 20
Method coverage: 70%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)66.67%6.05688.89%
startService(...)62.5%16.751685.71%
stopService(...)64.29%14.711484.62%
addObserver(...)0%110100%
servicesStartedAsync()100%210%
shutdown(...)100%11100%
run()93.75%45.823276.19%
startService(...)61.76%94.963462.5%
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)

/home/runner/work/ice/ice/csharp/src/iceboxnet/ServiceManagerI.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Collections;
 4using System.Diagnostics;
 5
 6namespace IceBox;
 7
 8//
 9// NOTE: the class isn't sealed on purpose to allow users to extend it.
 10//
 11internal class ServiceManagerI : ServiceManagerDisp_
 12{
 113    public ServiceManagerI(Ice.Communicator communicator, string[] args)
 14    {
 115        _communicator = communicator;
 116        _logger = _communicator.getLogger();
 17
 118        Ice.Properties props = _communicator.getProperties();
 19
 120        if (props.getIceProperty("Ice.Admin.Enabled").Length == 0)
 21        {
 122            _adminEnabled = props.getIceProperty("Ice.Admin.Endpoints").Length > 0;
 23        }
 24        else
 25        {
 026            _adminEnabled = props.getIcePropertyAsInt("Ice.Admin.Enabled") > 0;
 27        }
 28
 129        if (_adminEnabled)
 30        {
 131            string[] facetFilter = props.getIcePropertyAsList("Ice.Admin.Facets");
 132            if (facetFilter.Length > 0)
 33            {
 034                _adminFacetFilter = new HashSet<string>(facetFilter);
 35            }
 36            else
 37            {
 138                _adminFacetFilter = new HashSet<string>();
 39            }
 40        }
 41
 142        _argv = args;
 143        _traceServiceObserver = _communicator.getProperties().getIcePropertyAsInt("IceBox.Trace.ServiceObserver");
 144    }
 45
 46    public override void startService(string name, Ice.Current current)
 47    {
 148        var info = new ServiceInfo();
 149        lock (_mutex)
 50        {
 51            //
 52            // Search would be more efficient if services were contained in
 53            // a map, but order is required for shutdown.
 54            //
 55            int i;
 156            for (i = 0; i < _services.Count; ++i)
 57            {
 158                info = _services[i];
 159                if (info.name.Equals(name, StringComparison.Ordinal))
 60                {
 161                    if (_services[i].status != ServiceStatus.Stopped)
 62                    {
 063                        throw new AlreadyStartedException();
 64                    }
 165                    info.status = ServiceStatus.Starting;
 166                    _services[i] = info;
 167                    break;
 68                }
 69            }
 170            if (i == _services.Count)
 71            {
 072                throw new NoSuchServiceException();
 73            }
 174            _pendingStatusChanges = true;
 175        }
 76
 177        bool started = false;
 78        try
 79        {
 180            info.service.start(
 181                info.name,
 182                info.communicator ?? _sharedCommunicator,
 183                info.args);
 184            started = true;
 185        }
 086        catch (Exception e)
 87        {
 088            _logger.warning("ServiceManager: exception while starting service " + info.name + ":\n" + e.ToString());
 089        }
 90
 191        lock (_mutex)
 92        {
 93            int i;
 194            for (i = 0; i < _services.Count; ++i)
 95            {
 196                info = _services[i];
 197                if (info.name.Equals(name, StringComparison.Ordinal))
 98                {
 199                    if (started)
 100                    {
 1101                        info.status = ServiceStatus.Started;
 102
 1103                        var services = new List<string>
 1104                        {
 1105                            name
 1106                        };
 1107                        servicesStarted(services, _observers.Keys);
 108                    }
 109                    else
 110                    {
 0111                        info.status = ServiceStatus.Stopped;
 112                    }
 1113                    _services[i] = info;
 1114                    break;
 115                }
 116            }
 1117            _pendingStatusChanges = false;
 1118            Monitor.PulseAll(_mutex);
 1119        }
 1120    }
 121
 122    public override void stopService(string name, Ice.Current current)
 123    {
 1124        var info = new ServiceInfo();
 1125        lock (_mutex)
 126        {
 127            //
 128            // Search would be more efficient if services were contained in
 129            // a map, but order is required for shutdown.
 130            //
 131            int i;
 1132            for (i = 0; i < _services.Count; ++i)
 133            {
 1134                info = _services[i];
 1135                if (info.name.Equals(name, StringComparison.Ordinal))
 136                {
 1137                    if (info.status != ServiceStatus.Started)
 138                    {
 0139                        throw new AlreadyStoppedException();
 140                    }
 1141                    info.status = ServiceStatus.Stopping;
 1142                    _services[i] = info;
 1143                    break;
 144                }
 145            }
 1146            if (i == _services.Count)
 147            {
 0148                throw new NoSuchServiceException();
 149            }
 1150            _pendingStatusChanges = true;
 1151        }
 152
 1153        bool stopped = false;
 154        try
 155        {
 1156            info.service.stop();
 1157            stopped = true;
 1158        }
 0159        catch (Exception e)
 160        {
 0161            _logger.warning("ServiceManager: exception while stopping service " + info.name + "\n" + e.ToString());
 0162        }
 163
 1164        lock (_mutex)
 165        {
 166            int i;
 1167            for (i = 0; i < _services.Count; ++i)
 168            {
 1169                info = _services[i];
 1170                if (info.name.Equals(name, StringComparison.Ordinal))
 171                {
 1172                    if (stopped)
 173                    {
 1174                        info.status = ServiceStatus.Stopped;
 175
 1176                        var services = new List<string>
 1177                        {
 1178                            name
 1179                        };
 1180                        servicesStopped(services, _observers.Keys);
 181                    }
 182                    else
 183                    {
 0184                        info.status = ServiceStatus.Started;
 185                    }
 1186                    _services[i] = info;
 1187                    break;
 188                }
 189            }
 1190            _pendingStatusChanges = false;
 1191            Monitor.PulseAll(_mutex);
 1192        }
 1193    }
 194
 195    public override void addObserver(ServiceObserverPrx observer, Ice.Current current)
 196    {
 0197        var activeServices = new List<string>();
 198
 199        //
 200        // Null observers and duplicate registrations are ignored
 201        //
 0202        lock (_mutex)
 203        {
 0204            if (observer != null)
 205            {
 206                try
 207                {
 0208                    _observers.Add(observer, true);
 0209                }
 0210                catch (ArgumentException)
 211                {
 0212                    return;
 213                }
 214
 0215                if (_traceServiceObserver >= 1)
 216                {
 0217                    _logger.trace(
 0218                        "IceBox.ServiceObserver",
 0219                        "Added service observer " + _communicator.proxyToString(observer));
 220                }
 221
 0222                foreach (ServiceInfo info in _services)
 223                {
 0224                    if (info.status == ServiceStatus.Started)
 225                    {
 0226                        activeServices.Add(info.name);
 227                    }
 228                }
 229            }
 0230        }
 231
 0232        if (activeServices.Count > 0)
 233        {
 0234            _ = servicesStartedAsync(observer, activeServices.ToArray());
 235        }
 236
 237        async Task servicesStartedAsync(ServiceObserverPrx observer, string[] services)
 238        {
 239            try
 240            {
 0241                await observer.servicesStartedAsync(services).ConfigureAwait(false);
 0242            }
 0243            catch (System.Exception ex)
 244            {
 0245                removeObserver(observer, ex);
 0246            }
 0247        }
 0248    }
 249
 1250    public override void shutdown(Ice.Current current) => _communicator.shutdown();
 251
 252    public int run()
 253    {
 254        try
 255        {
 1256            Ice.Properties properties = _communicator.getProperties();
 257
 258            //
 259            // Parse the property set with the prefix "IceBox.Service.". These
 260            // properties should have the following format:
 261            //
 262            // IceBox.Service.Foo=<assembly>:Package.Foo [args]
 263            //
 264            // We parse the service properties specified in IceBox.LoadOrder
 265            // first, then the ones from remaining services.
 266            //
 1267            string prefix = "IceBox.Service.";
 1268            Dictionary<string, string> services = properties.getPropertiesForPrefix(prefix);
 269
 1270            if (services.Count == 0)
 271            {
 0272                throw new FailureException("ServiceManager: configuration must include at least one IceBox service.");
 273            }
 274
 1275            string[] loadOrder = properties.getIcePropertyAsList("IceBox.LoadOrder");
 1276            var servicesInfo = new List<StartServiceInfo>();
 1277            for (int i = 0; i < loadOrder.Length; ++i)
 278            {
 1279                if (loadOrder[i].Length > 0)
 280                {
 1281                    string key = prefix + loadOrder[i];
 1282                    if (!services.TryGetValue(key, out string value))
 283                    {
 0284                        throw new FailureException($"ServiceManager: no service definition for '{loadOrder[i]}'.");
 285                    }
 1286                    servicesInfo.Add(new StartServiceInfo(loadOrder[i], value, _argv));
 1287                    services.Remove(key);
 288                }
 289            }
 1290            foreach (KeyValuePair<string, string> entry in services)
 291            {
 1292                string name = entry.Key[prefix.Length..];
 1293                string value = entry.Value;
 1294                servicesInfo.Add(new StartServiceInfo(name, value, _argv));
 295            }
 296
 297            //
 298            // Check if some services are using the shared communicator in which
 299            // case we create the shared communicator now with a property set that
 300            // is the union of all the service properties (from services that use
 301            // the shared communicator).
 302            //
 1303            if (properties.getPropertiesForPrefix("IceBox.UseSharedCommunicator.").Count > 0)
 304            {
 1305                var initData = new Ice.InitializationData();
 1306                initData.properties = createServiceProperties("SharedCommunicator");
 1307                foreach (StartServiceInfo service in servicesInfo)
 308                {
 1309                    if (properties.getIcePropertyAsInt("IceBox.UseSharedCommunicator." + service.name) <= 0)
 310                    {
 311                        continue;
 312                    }
 313
 314                    //
 315                    // Load the service properties using the shared communicator properties as
 316                    // the default properties.
 317                    //
 1318                    var svcProperties = new Ice.Properties(ref service.args, initData.properties);
 319
 320                    //
 321                    // Remove properties from the shared property set that a service explicitly clears.
 322                    //
 1323                    Dictionary<string, string> allProps = initData.properties.getPropertiesForPrefix("");
 1324                    foreach (string key in allProps.Keys)
 325                    {
 1326                        if (svcProperties.getProperty(key).Length == 0)
 327                        {
 1328                            initData.properties.setProperty(key, "");
 329                        }
 330                    }
 331
 332                    //
 333                    // Add the service properties to the shared communicator properties.
 334                    //
 1335                    foreach (KeyValuePair<string, string> entry in svcProperties.getPropertiesForPrefix(""))
 336                    {
 1337                        initData.properties.setProperty(entry.Key, entry.Value);
 338                    }
 339
 340                    //
 341                    // Parse <service>.* command line options (the Ice command line options
 342                    // were parsed by the call to createProperties above).
 343                    //
 1344                    service.args = initData.properties.parseCommandLineOptions(service.name, service.args);
 345                }
 346
 1347                string facetNamePrefix = "IceBox.SharedCommunicator.";
 1348                bool addFacets = configureAdmin(initData.properties, facetNamePrefix);
 349
 1350                _sharedCommunicator = Ice.Util.initialize(initData);
 351
 1352                if (addFacets)
 353                {
 354                    // Add all facets created on shared communicator to the IceBox communicator
 355                    // but renamed <prefix>.<facet-name>, except for the Process facet which is
 356                    // never added.
 1357                    foreach (KeyValuePair<string, Ice.Object> p in _sharedCommunicator.findAllAdminFacets())
 358                    {
 1359                        if (p.Key != "Process")
 360                        {
 1361                            _communicator.addAdminFacet(p.Value, facetNamePrefix + p.Key);
 362                        }
 363                    }
 364                }
 365            }
 366
 1367            foreach (StartServiceInfo s in servicesInfo)
 368            {
 1369                startService(s.name, s.entryPoint, s.args);
 370            }
 371
 372            //
 373            // Start Admin (if enabled).
 374            //
 1375            _communicator.addAdminFacet(this, "IceBox.ServiceManager");
 1376            _communicator.getAdmin();
 377
 378            //
 379            // We may want to notify external scripts that the services
 380            // have started and that IceBox is "ready".
 381            // This is done by defining the property IceBox.PrintServicesReady=bundleName
 382            //
 383            // bundleName is whatever you choose to call this set of
 384            // services. It will be echoed back as "bundleName ready".
 385            //
 386            // This must be done after start() has been invoked on the
 387            // services.
 388            //
 1389            string bundleName = properties.getIceProperty("IceBox.PrintServicesReady");
 1390            if (bundleName.Length > 0)
 391            {
 1392                Console.Out.WriteLine(bundleName + " ready");
 393            }
 394
 1395            _communicator.waitForShutdown();
 1396        }
 0397        catch (FailureException ex)
 398        {
 0399            _logger.error(ex.ToString());
 0400            return 1;
 401        }
 0402        catch (Ice.CommunicatorDestroyedException)
 403        {
 404            // Expected if the communicator is destroyed
 0405        }
 0406        catch (Ice.ObjectAdapterDeactivatedException)
 407        {
 408            // Expected if the communicator is shutdown
 0409        }
 0410        catch (Ice.ObjectAdapterDestroyedException)
 411        {
 412            // Expected if the communicator is destroyed
 0413        }
 0414        catch (Exception ex)
 415        {
 0416            _logger.error("ServiceManager: caught exception:\n" + ex.ToString());
 0417            return 1;
 418        }
 419        finally
 420        {
 421            //
 422            // Invoke stop() on the services.
 423            //
 1424            stopAll();
 1425        }
 426
 1427        return 0;
 0428    }
 429
 430    private void startService(string service, string entryPoint, string[] args)
 431    {
 1432        lock (_mutex)
 433        {
 434            //
 435            // Extract the assembly name and the class name.
 436            //
 1437            string err = "ServiceManager: unable to load service '" + entryPoint + "': ";
 1438            int sepPos = entryPoint.IndexOf(':', StringComparison.Ordinal);
 1439            if (sepPos != -1)
 440            {
 441                const string driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 1442                if (entryPoint.Length > 3 &&
 1443                   sepPos == 1 &&
 1444                   driveLetters.Contains(entryPoint[0], StringComparison.Ordinal) &&
 1445                   (entryPoint[2] == '\\' || entryPoint[2] == '/'))
 446                {
 0447                    sepPos = entryPoint.IndexOf(':', 3);
 448                }
 449            }
 1450            if (sepPos == -1)
 451            {
 0452                throw new FailureException($"{err}invalid entry point format.");
 453            }
 454
 1455            System.Reflection.Assembly serviceAssembly = null;
 1456            string assemblyName = entryPoint[..sepPos];
 1457            string className = entryPoint[(sepPos + 1)..];
 458
 459            try
 460            {
 461                //
 462                // First try to load the assembly using Assembly.Load, which will succeed
 463                // if a fully-qualified name is provided or if a partial name has been qualified
 464                // in configuration. If that fails, try Assembly.LoadFrom(), which will succeed
 465                // if a file name is configured or a partial name is configured and DEVPATH is used.
 466                //
 467                try
 468                {
 1469                    serviceAssembly = System.Reflection.Assembly.Load(assemblyName);
 0470                }
 1471                catch (Exception ex)
 472                {
 473                    try
 474                    {
 1475                        serviceAssembly = System.Reflection.Assembly.LoadFrom(assemblyName);
 1476                    }
 0477                    catch (Exception)
 478                    {
 0479                        throw ex;
 480                    }
 1481                }
 1482            }
 0483            catch (Exception ex)
 484            {
 0485                throw new FailureException($"{err}unable to load assembly: {assemblyName}", ex);
 486            }
 487
 488            //
 489            // Instantiate the class.
 490            //
 1491            Type c = null;
 492            try
 493            {
 1494                c = serviceAssembly.GetType(className, true);
 1495            }
 0496            catch (Exception ex)
 497            {
 0498                throw new FailureException($"{err}GetType failed for '{className}'.", ex);
 499            }
 500
 1501            var info = new ServiceInfo();
 1502            info.name = service;
 1503            info.status = ServiceStatus.Stopped;
 1504            info.args = args;
 505
 506            //
 507            // If IceBox.UseSharedCommunicator.<name> is defined, create a
 508            // communicator for the service. The communicator inherits
 509            // from the shared communicator properties. If it's not
 510            // defined, add the service properties to the shared
 511            // communicator property set.
 512            //
 513            Ice.Communicator communicator;
 1514            if (_communicator.getProperties().getIcePropertyAsInt("IceBox.UseSharedCommunicator." + service) > 0)
 515            {
 516                Debug.Assert(_sharedCommunicator != null);
 1517                communicator = _sharedCommunicator;
 518            }
 519            else
 520            {
 521                //
 522                // Create the service properties. We use the communicator properties as the default
 523                // properties if IceBox.InheritProperties is set.
 524                //
 1525                var initData = new Ice.InitializationData();
 1526                initData.properties = createServiceProperties(service);
 1527                if (info.args.Length > 0)
 528                {
 529                    //
 530                    // Create the service properties with the given service arguments. This should
 531                    // read the service config file if it's specified with --Ice.Config.
 532                    //
 1533                    initData.properties = new Ice.Properties(ref info.args, initData.properties);
 534
 535                    //
 536                    // Next, parse the service "<service>.*" command line options (the Ice command
 537                    // line options were parsed by the createProperties above)
 538                    //
 1539                    info.args = initData.properties.parseCommandLineOptions(service, info.args);
 540                }
 541
 542                //
 543                // Clone the logger to assign a new prefix. If one of the built-in loggers is configured
 544                // don't set any logger.
 545                //
 1546                if (initData.properties.getIceProperty("Ice.LogFile").Length == 0)
 547                {
 1548                    initData.logger = _logger.cloneWithPrefix(initData.properties.getIceProperty("Ice.ProgramName"));
 549                }
 550
 551                //
 552                // If Admin is enabled on the IceBox communicator, for each service that does not set
 553                // Ice.Admin.Enabled, we set Ice.Admin.Enabled=1 to have this service create facets; then
 554                // we add these facets to the IceBox Admin object as IceBox.Service.<service>.<facet>.
 555                //
 1556                string serviceFacetNamePrefix = "IceBox.Service." + service + ".";
 1557                bool addFacets = configureAdmin(initData.properties, serviceFacetNamePrefix);
 558
 1559                info.communicator = Ice.Util.initialize(initData);
 1560                communicator = info.communicator;
 561
 1562                if (addFacets)
 563                {
 564                    // Add all facets created on the service communicator to the IceBox communicator
 565                    // but renamed IceBox.Service.<service>.<facet-name>, except for the Process facet
 566                    // which is never added
 1567                    foreach (KeyValuePair<string, Ice.Object> p in communicator.findAllAdminFacets())
 568                    {
 1569                        if (p.Key != "Process")
 570                        {
 1571                            _communicator.addAdminFacet(p.Value, serviceFacetNamePrefix + p.Key);
 572                        }
 573                    }
 574                }
 575            }
 576
 577            try
 578            {
 579                //
 580                // Instantiate the service.
 581                //
 582                try
 583                {
 584                    //
 585                    // If the service class provides a constructor that accepts an Ice.Communicator argument,
 586                    // use that in preference to the default constructor.
 587                    //
 1588                    var parameterTypes = new Type[1];
 1589                    parameterTypes[0] = typeof(Ice.Communicator);
 1590                    System.Reflection.ConstructorInfo ci = c.GetConstructor(parameterTypes);
 1591                    if (ci != null)
 592                    {
 593                        try
 594                        {
 1595                            object[] parameters = new object[1];
 1596                            parameters[0] = _communicator;
 1597                            info.service = (Service)ci.Invoke(parameters);
 1598                        }
 0599                        catch (MethodAccessException ex)
 600                        {
 0601                            throw new FailureException(
 0602                                $"{err}unable to access service constructor {className}(Ice.Communicator).",
 0603                                ex);
 604                        }
 605                    }
 606                    else
 607                    {
 608                        //
 609                        // Fall back to the default constructor.
 610                        //
 611                        try
 612                        {
 1613                            info.service = (Service)Ice.Internal.AssemblyUtil.createInstance(c);
 1614                            if (info.service == null)
 615                            {
 0616                                throw new FailureException($"{err}no default constructor for '{className}'.");
 617                            }
 1618                        }
 0619                        catch (UnauthorizedAccessException ex)
 620                        {
 0621                            throw new FailureException(
 0622                                $"{err}unauthorized access to default service constructor for '{className}'.", ex);
 623                        }
 624                    }
 1625                }
 0626                catch (FailureException)
 627                {
 0628                    throw;
 629                }
 0630                catch (InvalidCastException ex)
 631                {
 0632                    throw new FailureException($"{err}service does not implement IceBox.Service.", ex);
 633                }
 0634                catch (System.Reflection.TargetInvocationException ex)
 635                {
 0636                    if (ex.InnerException is FailureException)
 637                    {
 0638                        throw ex.InnerException;
 639                    }
 640                    else
 641                    {
 0642                        throw new FailureException(
 0643                            $"{err}exception in service constructor for '{className}'.", ex.InnerException);
 644                    }
 645                }
 0646                catch (Exception ex)
 647                {
 0648                    throw new FailureException($"{err}exception in service constructor for '{className}'.", ex);
 649                }
 650
 651                try
 652                {
 1653                    info.service.start(service, communicator, info.args);
 1654                }
 0655                catch (FailureException)
 656                {
 0657                    throw;
 658                }
 0659                catch (Exception ex)
 660                {
 0661                    throw new FailureException($"{err}exception while starting service '{service}'.", ex);
 662                }
 663
 1664                info.status = ServiceStatus.Started;
 1665                _services.Add(info);
 1666            }
 0667            catch (Exception)
 668            {
 0669                if (info.communicator != null)
 670                {
 0671                    destroyServiceCommunicator(service, info.communicator);
 672                }
 0673                throw;
 674            }
 675        }
 1676    }
 677
 678    private void stopAll()
 679    {
 1680        lock (_mutex)
 681        {
 682            //
 683            // First wait for any active startService/stopService calls to complete.
 684            //
 1685            while (_pendingStatusChanges)
 686            {
 0687                Monitor.Wait(_mutex);
 688            }
 689
 690            //
 691            // For each service, we call stop on the service.
 692            // Services are stopped in the reverse order of the order they were started.
 693            //
 1694            _services.Reverse();
 1695            var stoppedServices = new List<string>();
 1696            foreach (ServiceInfo info in _services)
 697            {
 1698                if (info.status == ServiceStatus.Started)
 699                {
 700                    try
 701                    {
 1702                        info.service.stop();
 1703                        stoppedServices.Add(info.name);
 1704                    }
 0705                    catch (Exception e)
 706                    {
 0707                        _logger.warning(
 0708                            "IceBox.ServiceManager: exception while stopping service " +
 0709                                        info.name +
 0710                                        ":\n" +
 0711                                        e.ToString());
 0712                    }
 713                }
 714
 1715                if (info.communicator != null)
 716                {
 1717                    destroyServiceCommunicator(info.name, info.communicator);
 718                }
 719            }
 720
 1721            if (_sharedCommunicator != null)
 722            {
 1723                removeAdminFacets("IceBox.SharedCommunicator.");
 724
 725                try
 726                {
 1727                    _sharedCommunicator.destroy();
 1728                }
 0729                catch (Exception e)
 730                {
 0731                    _logger.warning(
 0732                        "ServiceManager: exception while destroying shared communicator:\n" + e.ToString());
 0733                }
 1734                _sharedCommunicator = null;
 735            }
 736
 1737            _services.Clear();
 1738            servicesStopped(stoppedServices, _observers.Keys);
 1739        }
 1740    }
 741
 742    private void servicesStarted(List<string> services, Dictionary<ServiceObserverPrx, bool>.KeyCollection observers)
 743    {
 744        //
 745        // Must be called with 'this' unlocked
 746        //
 747
 1748        if (services.Count > 0)
 749        {
 1750            string[] servicesArray = services.ToArray();
 751
 1752            foreach (ServiceObserverPrx observer in observers)
 753            {
 0754                _ = servicesStartedAsync(observer, servicesArray);
 755            }
 756        }
 757
 758        async Task servicesStartedAsync(ServiceObserverPrx observer, string[] services)
 759        {
 760            try
 761            {
 0762                await observer.servicesStartedAsync(services).ConfigureAwait(false);
 0763            }
 0764            catch (System.Exception ex)
 765            {
 0766                removeObserver(observer, ex);
 0767            }
 0768        }
 1769    }
 770
 771    private void servicesStopped(List<string> services, Dictionary<ServiceObserverPrx, bool>.KeyCollection observers)
 772    {
 773        //
 774        // Must be called with 'this' unlocked
 775        //
 776
 1777        if (services.Count > 0)
 778        {
 1779            string[] servicesArray = services.ToArray();
 780
 1781            foreach (ServiceObserverPrx observer in observers)
 782            {
 0783                _ = servicesStoppedAsync(observer, servicesArray);
 784            }
 785        }
 786
 787        async Task servicesStoppedAsync(ServiceObserverPrx observer, string[] services)
 788        {
 789            try
 790            {
 0791                await observer.servicesStoppedAsync(services).ConfigureAwait(false);
 0792            }
 0793            catch (System.Exception ex)
 794            {
 0795                removeObserver(observer, ex);
 0796            }
 0797        }
 1798    }
 799
 800    private void
 801    removeObserver(ServiceObserverPrx observer, System.Exception ex)
 802    {
 0803        lock (_mutex)
 804        {
 0805            if (_observers.Remove(observer))
 806            {
 0807                observerRemoved(observer, ex);
 808            }
 0809        }
 0810    }
 811
 812    private void observerRemoved(ServiceObserverPrx observer, Exception ex)
 813    {
 0814        if (_traceServiceObserver >= 1)
 815        {
 816            //
 817            // CommunicatorDestroyedException may occur during shutdown. The observer notification has
 818            // been sent, but the communicator was destroyed before the reply was received. We do not
 819            // log a message for this exception.
 820            //
 0821            if (!(ex is Ice.CommunicatorDestroyedException))
 822            {
 0823                _logger.trace(
 0824                    "IceBox.ServiceObserver",
 0825                    "Removed service observer " + _communicator.proxyToString(observer)
 0826                    + "\nafter catching " + ex.ToString());
 827            }
 828        }
 0829    }
 830
 831    private enum ServiceStatus
 832    {
 833        Stopping,
 834        Stopped,
 835        Starting,
 836        Started
 837    }
 838
 839    private struct ServiceInfo
 840    {
 841        public string name;
 842        public Service service;
 843        public Ice.Communicator communicator;
 844        public ServiceStatus status;
 845        public string[] args;
 846    }
 847
 848    private class StartServiceInfo
 849    {
 1850        public StartServiceInfo(string service, string value, string[] serverArgs)
 851        {
 852            //
 853            // Separate the entry point from the arguments.
 854            //
 1855            name = service;
 856
 857            try
 858            {
 1859                args = Ice.UtilInternal.Options.split(value);
 1860            }
 0861            catch (Ice.ParseException ex)
 862            {
 0863                throw new FailureException($"ServiceManager: invalid arguments for service '{name}'.", ex);
 864            }
 865
 866            Debug.Assert(args.Length > 0);
 867
 1868            entryPoint = args[0];
 869
 870            //
 871            // Shift the arguments.
 872            //
 1873            string[] tmp = new string[args.Length - 1];
 1874            Array.Copy(args, 1, tmp, 0, args.Length - 1);
 1875            args = tmp;
 876
 1877            if (serverArgs.Length > 0)
 878            {
 0879                var l = new ArrayList();
 0880                for (int j = 0; j < args.Length; j++)
 881                {
 0882                    l.Add(args[j]);
 883                }
 0884                for (int j = 0; j < serverArgs.Length; j++)
 885                {
 0886                    if (serverArgs[j].StartsWith("--" + service + ".", StringComparison.Ordinal))
 887                    {
 0888                        l.Add(serverArgs[j]);
 889                    }
 890                }
 0891                args = (string[])l.ToArray(typeof(string));
 892            }
 1893        }
 894
 895        public string name;
 896        public string entryPoint;
 897        public string[] args;
 898    }
 899
 900    private Ice.Properties createServiceProperties(string service)
 901    {
 1902        var properties = new Ice.Properties();
 1903        Ice.Properties communicatorProperties = _communicator.getProperties();
 1904        if (communicatorProperties.getIcePropertyAsInt("IceBox.InheritProperties") > 0)
 905        {
 906            // Inherit all except IceBox. and Ice.Admin. properties
 1907            foreach (KeyValuePair<string, string> property in communicatorProperties.getPropertiesForPrefix(""))
 908            {
 1909                if (!property.Key.StartsWith("IceBox.", StringComparison.Ordinal) &&
 1910                    !property.Key.StartsWith("Ice.Admin.", StringComparison.Ordinal))
 911                {
 1912                    properties.setProperty(property.Key, property.Value);
 913                }
 914            }
 915        }
 916
 1917        string programName = communicatorProperties.getIceProperty("Ice.ProgramName");
 1918        if (programName.Length == 0)
 919        {
 0920            properties.setProperty("Ice.ProgramName", service);
 921        }
 922        else
 923        {
 1924            properties.setProperty("Ice.ProgramName", programName + "-" + service);
 925        }
 1926        return properties;
 927    }
 928
 929    private void destroyServiceCommunicator(string service, Ice.Communicator communicator)
 930    {
 1931        if (communicator != null)
 932        {
 933            try
 934            {
 1935                communicator.shutdown();
 1936                communicator.waitForShutdown();
 1937            }
 0938            catch (Ice.CommunicatorDestroyedException)
 939            {
 940                //
 941                // Ignore, the service might have already destroyed
 942                // the communicator for its own reasons.
 943                //
 0944            }
 0945            catch (Exception e)
 946            {
 0947                _logger.warning("ServiceManager: exception while shutting down communicator for service "
 0948                                + service + "\n" + e.ToString());
 0949            }
 950
 1951            removeAdminFacets("IceBox.Service." + service + ".");
 1952            communicator.destroy();
 953        }
 1954    }
 955
 956    private bool configureAdmin(Ice.Properties properties, string prefix)
 957    {
 1958        if (_adminEnabled && properties.getIceProperty("Ice.Admin.Enabled").Length == 0)
 959        {
 1960            var facetNames = new List<string>();
 1961            foreach (string p in _adminFacetFilter)
 962            {
 0963                if (p.StartsWith(prefix, StringComparison.Ordinal))
 964                {
 0965                    facetNames.Add(p[prefix.Length..]);
 966                }
 967            }
 968
 1969            if (_adminFacetFilter.Count == 0 || facetNames.Count > 0)
 970            {
 1971                properties.setProperty("Ice.Admin.Enabled", "1");
 972
 1973                if (facetNames.Count > 0)
 974                {
 975                    // TODO: need String.Join with escape!
 0976                    properties.setProperty("Ice.Admin.Facets", string.Join(" ", facetNames.ToArray()));
 977                }
 1978                return true;
 979            }
 980        }
 0981        return false;
 982    }
 983
 984    private void removeAdminFacets(string prefix)
 985    {
 986        try
 987        {
 1988            foreach (string p in _communicator.findAllAdminFacets().Keys)
 989            {
 1990                if (p.StartsWith(prefix, StringComparison.Ordinal))
 991                {
 1992                    _communicator.removeAdminFacet(p);
 993                }
 994            }
 1995        }
 0996        catch (Ice.CommunicatorDestroyedException)
 997        {
 998            // Ignored
 0999        }
 01000        catch (Ice.ObjectAdapterDeactivatedException)
 1001        {
 1002            // Ignored
 01003        }
 01004        catch (Ice.ObjectAdapterDestroyedException)
 1005        {
 1006            // Ignored
 01007        }
 11008    }
 1009
 1010    private readonly Ice.Communicator _communicator;
 1011    private readonly bool _adminEnabled;
 1012    private readonly HashSet<string> _adminFacetFilter;
 1013    private Ice.Communicator _sharedCommunicator;
 1014    private readonly Ice.Logger _logger;
 1015    private readonly string[] _argv; // Filtered server argument vector
 11016    private readonly List<ServiceInfo> _services = new();
 1017    private bool _pendingStatusChanges;
 11018    private readonly Dictionary<ServiceObserverPrx, bool> _observers = new();
 1019    private readonly int _traceServiceObserver;
 11020    private readonly object _mutex = new();
 1021}