private void broadcastShutdown()
    {
        Log.debug( "Notifying all local users about the imminent destruction of chat service '{}'", chatServiceName );

        final Set<OccupantManager.Occupant> localOccupants = occupantManager.getLocalOccupants();

        if (localOccupants.isEmpty()) {
            return;
        }

        // A thread pool is used to broadcast concurrently, as well as to limit the execution time of this service.
        final ThreadFactory threadFactory = new NamedThreadFactory("MUC-Shutdown-", Executors.defaultThreadFactory(), false, Thread.NORM_PRIORITY);
        final ExecutorService service = Executors.newFixedThreadPool( Math.min( localOccupants.size(), 10 ), threadFactory );

        // Queue all tasks in the executor service.
        for ( final OccupantManager.Occupant localOccupant : localOccupants )
        {
            service.submit(() -> {
                try
                {
                    // Obtaining the room without acquiring a lock. Usage of the room is read-only (the implementation below
                    // should not modify the room state in a way that the cluster cares about), and more importantly, speed
                    // is of importance (waiting for every room's lock to be acquired would slow down the shutdown process).
                    // Lastly, this service is shutting down (likely because the server is shutting down). The trade-off
                    // between speed and access of room state while not holding a lock seems worth while here.
                    final MUCRoom room = getChatRoom(localOccupant.getRoomName());
                    if (room == null) {
                        // Mismatch between MUCUser#getRooms() and MUCRoom#localMUCRoomManager ?
                        Log.warn("User '{}' appears to have been an occupant of room '{}' of service '{}' that room does not seem to exist.", localOccupant.getRealJID(), localOccupant.getRoomName(), chatServiceName);
                        return;
                    }
                    final MUCOccupant occupant = room.getOccupantByFullJID(localOccupant.getRealJID());
                    if (occupant == null) {
                        // Mismatch between MUCUser#getRooms() and MUCRoom#occupants ?
                        Log.warn("User '{}' appears to have been an occupant of room '{}' of service '{}' but the associated occupant data does not seem to exist.", localOccupant.getRealJID(), localOccupant.getRoomName(), chatServiceName);
                        return;
                    }

                    // Send a presence stanza of type "unavailable" to the occupant
                    final Presence presence = room.createPresence( Presence.Type.unavailable );
                    presence.setFrom( occupant.getOccupantJID() );

                    // A fragment containing the x-extension.
                    final Element fragment = presence.addChildElement( "x", "http://jabber.org/protocol/muc#user" );
                    final Element item = fragment.addElement( "item" );
                    item.addAttribute( "affiliation", "none" );
                    item.addAttribute( "role", "none" );
                    fragment.addElement( "status" ).addAttribute( "code", "332" );

                    // Make sure that the presence change for each user is only sent to that user (and not broadcast in the room)!
                    // Not needed to create a defensive copy of the stanza. It's not used anywhere else.
                    occupant.send( presence );

                    // Let all other cluster nodes know!
                    room.removeOccupant(occupant);
                }
                catch ( final Exception e )
                {
                    Log.debug( "Unable to inform {} about the imminent destruction of chat service '{}'", localOccupant.realJID, chatServiceName, e );
                }
            });
        }

        // Try to shutdown - wait - force shutdown.
        service.shutdown();
        try
        {
            if (service.awaitTermination( JiveGlobals.getIntProperty( "xmpp.muc.await-termination-millis", 500 ), TimeUnit.MILLISECONDS )) {
                Log.debug("Successfully notified all local users about the imminent destruction of chat service '{}'", chatServiceName);
            } else {
                Log.debug("Unable to notify all local users about the imminent destruction of chat service '{}' (timeout)", chatServiceName);
            }
        }
        catch ( final InterruptedException e )
        {
            Log.debug( "Interrupted while waiting for all users to be notified of shutdown of chat service '{}'. Shutting down immediately.", chatServiceName );
        }
        service.shutdownNow();
    }
Informs all users local to this cluster node that he or she is being removed from the room because the MUC
service is being shut down.

The implementation is optimized to run as fast as possible (to prevent prolonging the shutdown).
xmppserver/src/main/java/org/jivesoftware/openfire/muc/spi/MultiUserChatServiceImpl.java