view tango/tango/io/selector/PollSelector.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
line wrap: on
line source

/*******************************************************************************
  copyright:   Copyright (c) 2006 Juan Jose Comellas. All rights reserved
  license:     BSD style: $(LICENSE)
  author:      Juan Jose Comellas <juanjo@comellas.com.ar>
*******************************************************************************/

module tango.io.selector.PollSelector;

version (Posix)
{
    public import tango.io.model.IConduit;

    private import tango.io.selector.model.ISelector;
    private import tango.io.selector.AbstractSelector;
    private import tango.io.selector.SelectorException;
    private import tango.sys.Common;
    private import tango.stdc.errno;

    version (linux)
        private import tango.sys.linux.linux;

    debug (selector)
        private import tango.io.Stdout;


    /**
     * Selector that uses the poll() system call to receive I/O events for
     * the registered conduits. To use this class you would normally do
     * something like this:
     *
     * Examples:
     * ---
     * import tango.io.selector.PollSelector;
     *
     * Socket socket;
     * ISelector selector = new PollSelector();
     *
     * selector.open(100, 10);
     *
     * // Register to read from socket
     * selector.register(socket, Event.Read);
     *
     * int eventCount = selector.select(0.1); // 0.1 seconds
     * if (eventCount > 0)
     * {
     *     // We can now read from the socket
     *     socket.read();
     * }
     * else if (eventCount == 0)
     * {
     *     // Timeout
     * }
     * else if (eventCount == -1)
     * {
     *     // Another thread called the wakeup() method.
     * }
     * else
     * {
     *     // Error: should never happen.
     * }
     *
     * selector.close();
     * ---
     */
    public class PollSelector: AbstractSelector
    {
        /**
         * Alias for the select() method as we're not reimplementing it in
         * this class.
         */
        alias AbstractSelector.select select;

        /**
         * Default number of SelectionKey's that will be handled by the
         * PollSelector.
         */
        public const uint DefaultSize = 64;

        /** Map to associate the conduit handles with their selection keys */
        private PollSelectionKey[ISelectable.Handle] _keys;
        private SelectionKey[] _selectedKeys;
        private pollfd[] _pfds;
        private uint _count = 0;
        private int _eventCount = 0;

        /**
         * Open the poll()-based selector.
         *
         * Params:
         * size         = maximum amount of conduits that will be registered;
         *                it will grow dynamically if needed.
         * maxEvents    = maximum amount of conduit events that will be
         *                returned in the selection set per call to select();
         *                this value is currently not used by this selector.
         */
        public void open(uint size = DefaultSize, uint maxEvents = DefaultSize)
        in
        {
            assert(size > 0);
        }
        body
        {
            _pfds = new pollfd[size];
        }

        /**
         * Close the selector.
         *
         * Remarks:
         * It can be called multiple times without harmful side-effects.
         */
        public void close()
        {
            _keys = null;
            _selectedKeys = null;
            _pfds = null;
            _count = 0;
            _eventCount = 0;
        }

        /**
         * Associate a conduit to the selector and track specific I/O events.
         *
         * Params:
         * conduit      = conduit that will be associated to the selector;
         *                must be a valid conduit (i.e. not null and open).
         * events       = bit mask of Event values that represent the events
         *                that will be tracked for the conduit.
         * attachment   = optional object with application-specific data that
         *                will be available when an event is triggered for the
         *                conduit
         *
         * Throws:
         * RegisteredConduitException if the conduit had already been
         * registered to the selector.
         *
         * Examples:
         * ---
         * selector.register(conduit, Event.Read | Event.Write, object);
         * ---
         */
        public void register(ISelectable conduit, Event events, Object attachment = null)
        in
        {
            assert(conduit !is null && conduit.fileHandle() >= 0);
        }
        body
        {
            debug (selector)
                Stdout.format("--- PollSelector.register(handle={0}, events=0x{1:x})\n",
                              cast(int) conduit.fileHandle(), cast(uint) events);

            // We make sure that the conduit is not already registered to
            // the Selector
            if ((conduit.fileHandle() in _keys) is null)
            {
                if (_count == _pfds.length)
                    _pfds.length = _pfds.length + 1;

                _pfds[_count].fd = conduit.fileHandle();
                _pfds[_count].events = cast(short) events;
                _pfds[_count].revents = 0;

                _keys[conduit.fileHandle()] = new PollSelectionKey(conduit, events, _count, attachment);
                _count++;
            }
            else
            {
                throw new RegisteredConduitException(__FILE__, __LINE__);
            }
        }

        /**
         * Modify the events that are being tracked or the 'attachment' field
         * for an already registered conduit.
         *
         * Params:
         * conduit      = conduit that will be associated to the selector;
         *                must be a valid conduit (i.e. not null and open).
         * events       = bit mask of Event values that represent the events
         *                that will be tracked for the conduit.
         * attachment   = optional object with application-specific data that
         *                will be available when an event is triggered for the
         *                conduit
         *
         * Remarks:
         * The 'attachment' member of the SelectionKey will always be
         * overwritten, even if it's null.
         *
         * Throws:
         * UnregisteredConduitException if the conduit had not been previously
         * registered to the selector.
         *
         * Examples:
         * ---
         * selector.reregister(conduit, Event.Write, object);
         * ---
         */
        public void reregister(ISelectable conduit, Event events, Object attachment = null)
        in
        {
            assert(conduit !is null && conduit.fileHandle() >= 0);
        }
        body
        {
            debug (selector)
                Stdout.format("--- PollSelector.reregister(handle={0}, events=0x{1:x})",
                              cast(int) conduit.fileHandle(), cast(uint) events);

            PollSelectionKey* current = (conduit.fileHandle() in _keys);

            if (current !is null)
            {
                debug (selector)
                    Stdout.format("--- Adding pollfd in index {0} (of {1})\n",
                                  current.index, _count);

                (*current).events = events;
                (*current).attachment = attachment;

                _pfds[current.index].events = cast(short) events;
            }
            else
            {
                throw new UnregisteredConduitException(__FILE__, __LINE__);
            }
        }

        /**
         * Remove a conduit from the selector.
         *
         * Params:
         * conduit      = conduit that had been previously associated to the
         *                selector; it can be null.
         *
         * Remarks:
         * Unregistering a null conduit is allowed and no exception is thrown
         * if this happens.
         *
         * Throws:
         * UnregisteredConduitException if the conduit had not been previously
         * registered to the selector.
         */
        public void unregister(ISelectable conduit)
        {
            if (conduit !is null)
            {
                try
                {
                    debug (selector)
                        Stdout.format("--- PollSelector.unregister(handle={0})\n",
                                      cast(int) conduit.fileHandle());

                    PollSelectionKey* removed = (conduit.fileHandle() in _keys);

                    if (removed !is null)
                    {
                        debug (selector)
                            Stdout.format("--- Removing pollfd in index {0} (of {1})\n",
                                          removed.index, _count);

                        for (uint i = removed.index + 1; i > 0 && i < _count; i++)
                        {
                            _pfds[i - 1] = _pfds[i];
                        }
                        _count--;

                        _keys.remove(conduit.fileHandle());
                    }
                    else
                    {
                        debug (selector)
                            Stdout.format("--- PollSelector.unregister(handle={0}): conduit was not found\n",
                                          cast(int) conduit.fileHandle());
                        throw new UnregisteredConduitException(__FILE__, __LINE__);
                    }
                }
                catch (Exception e)
                {
                    debug (selector)
                        Stdout.format("--- Exception inside PollSelector.unregister(handle={0}): {1}",
                                      cast(int) conduit.fileHandle(), e.toString());

                    throw new UnregisteredConduitException(__FILE__, __LINE__);
                }
            }
        }

        /**
         * Wait for I/O events from the registered conduits for a specified
         * amount of time.
         *
         * Params:
         * timeout  = Timespan with the maximum amount of time that the
         *            selector will wait for events from the conduits; the
         *            amount of time is relative to the current system time
         *            (i.e. just the number of milliseconds that the selector
         *            has to wait for the events).
         *
         * Returns:
         * The amount of conduits that have received events; 0 if no conduits
         * have received events within the specified timeout; and -1 if the
         * wakeup() method has been called from another thread.
         *
         * Throws:
         * InterruptedSystemCallException if the underlying system call was
         * interrupted by a signal and the 'restartInterruptedSystemCall'
         * property was set to false; SelectorException if there were no
         * resources available to wait for events from the conduits.
         */
        public int select(TimeSpan timeout)
        {
            int to = (timeout != TimeSpan.max ? cast(int) timeout.millis : -1);

            debug (selector)
                Stdout.format("--- PollSelector.select({0} ms): waiting on {1} handles\n",
                              to, _count);

            // We run the call to poll() inside a loop in case the system call
            // was interrupted by a signal and we need to restart it.
            while (true)
            {
                _eventCount = poll(_pfds.ptr, _count, to);
                if (_eventCount > 0)
                {
                    int i = 0;
                    PollSelectionKey* key;

                    if (_selectedKeys is null)
                    {
                        _selectedKeys = new SelectionKey[16];
                    }

                    // FIXME: add support for the wakeup() call.
                    foreach (pollfd pfd; _pfds[0 .. _count])
                    {
                        if (i < _eventCount)
                        {
                            if (pfd.revents != 0)
                            {
                                debug (selector)
                                    Stdout.format("--- Found events 0x{0:x} for handle {1} (index {2})\n",
                                                  cast(uint) pfd.revents, cast(int) pfd.fd, i);

                                // Find the key whose handle received an event
                                key = ((cast(ISelectable.Handle) pfd.fd) in _keys);
                                if (key !is null)
                                {
                                    // Enlarge the array of necessary
                                    if (i >= _selectedKeys.length)
                                    {
                                        // The underlying array worries about
                                        // incrementing the allocated block
                                        // efficiently.
                                        _selectedKeys.length = i + 1;
                                    }

                                    (*key).events = cast(Event) pfd.revents;

                                    _selectedKeys[i] = *key;
                                    i++;
                                }
                                else
                                {
                                    debug (selector)
                                        Stdout.format("--- Handle {0} was not found in the Selector\n",
                                                      cast(int) pfd.fd);
                                }
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                    _selectedKeys.length = i;
                    break;
                }
                else if (_eventCount == 0)
                {
                    // Timeout
                    break;
                }
                else // if (eventCount < 0)
                {
                    if (errno != EINTR || !_restartInterruptedSystemCall)
                    {
                        // The call to checkErrno() ends up throwing an exception
                        checkErrno(__FILE__, __LINE__);
                    }
                    debug (selector)
                        Stdout.print("--- Restarting poll() after being interrupted\n");
                }
            }
            return _eventCount;
        }

        /**
         * Return the selection set resulting from the call to any of the
         * select() methods.
         *
         * Remarks:
         * If the call to select() was unsuccessful or it did not return any
         * events, the returned value will be null.
         */
        public ISelectionSet selectedSet()
        {
            return (_eventCount > 0 ? new PollSelectionSet(_selectedKeys) : null);
        }

        /**
         * Return the selection key resulting from the registration of a
         * conduit to the selector.
         *
         * Remarks:
         * If the conduit is not registered to the selector the returned
         * value will be null. No exception will be thrown by this method.
         */
        public SelectionKey key(ISelectable conduit)
        {
            return (conduit !is null ? _keys[conduit.fileHandle()] : null);
        }

        unittest
        {
        }
    }

    /**
     * Class used to hold the list of Conduits that have received events.
     */
    private class PollSelectionSet: ISelectionSet
    {
        private SelectionKey[] _keys;

        protected this(SelectionKey[] keys)
        {
            _keys = keys;
        }

        public uint length()
        {
            return _keys.length;
        }

        /**
         * Iterate over all the Conduits that have received events.
         */
        public int opApply(int delegate(inout SelectionKey) dg)
        {
            int rc = 0;

            foreach (SelectionKey current; _keys)
            {
                if (dg(current) != 0)
                {
                    rc = -1;
                    break;
                }
            }
            return rc;
        }
    }

    /**
     * Class that holds the information that the PollSelector needs to deal
     * with each registered Conduit.
     */
    private class PollSelectionKey: SelectionKey
    {
        private uint _index;

        public this()
        {
        }

        public this(ISelectable conduit, Event events, uint index, Object attachment)
        {
            super(conduit, events, attachment);

            _index = index;
        }

        public uint index()
        {
            return _index;
        }

        public void index(uint index)
        {
            _index = index;
        }
    }
}