Browse Source

[master] Merge branch 'trac1678'

Jelte Jansen 13 years ago
parent
commit
04f9d8ebe5

+ 2 - 0
ext/asio/asio/detail/impl/kqueue_reactor.ipp

@@ -301,12 +301,14 @@ void kqueue_reactor::run(bool block, op_queue<operation>& ops)
               EV_ADD | EV_ONESHOT, EV_OOBAND, 0, descriptor_data);
         else
           continue;
+        break;
       case EVFILT_WRITE:
         if (!descriptor_data->op_queue_[write_op].empty())
           ASIO_KQUEUE_EV_SET(&event, descriptor, EVFILT_WRITE,
               EV_ADD | EV_ONESHOT, 0, 0, descriptor_data);
         else
           continue;
+        break;
       default:
         break;
       }

+ 18 - 18
ext/asio/asio/detail/impl/socket_ops.ipp

@@ -282,15 +282,26 @@ int close(socket_type s, state_type& state,
   int result = 0;
   if (s != invalid_socket)
   {
-#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
-    if ((state & non_blocking) && (state & user_set_linger))
+    if (destruction && (state & user_set_linger))
     {
-      ioctl_arg_type arg = 0;
-      ::ioctlsocket(s, FIONBIO, &arg);
-      state &= ~non_blocking;
+      ::linger opt;
+      opt.l_onoff = 0;
+      opt.l_linger = 0;
+      asio::error_code ignored_ec;
+      socket_ops::setsockopt(s, state, SOL_SOCKET,
+          SO_LINGER, &opt, sizeof(opt), ignored_ec);
     }
+
+    clear_last_error();
+#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+    result = error_wrapper(::closesocket(s), ec);
 #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
-    if (state & non_blocking)
+    result = error_wrapper(::close(s), ec);
+#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+
+    if (result != 0
+        && (ec == asio::error::would_block
+          || ec == asio::error::try_again))
     {
 #if defined(__SYMBIAN32__)
       int flags = ::fcntl(s, F_GETFL, 0);
@@ -301,18 +312,6 @@ int close(socket_type s, state_type& state,
       ::ioctl(s, FIONBIO, &arg);
 #endif // defined(__SYMBIAN32__)
       state &= ~non_blocking;
-    }
-#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
-
-    if (destruction && (state & user_set_linger))
-    {
-      ::linger opt;
-      opt.l_onoff = 0;
-      opt.l_linger = 0;
-      asio::error_code ignored_ec;
-      socket_ops::setsockopt(s, state, SOL_SOCKET,
-          SO_LINGER, &opt, sizeof(opt), ignored_ec);
-    }
 
     clear_last_error();
 #if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
@@ -320,6 +319,7 @@ int close(socket_type s, state_type& state,
 #else // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
     result = error_wrapper(::close(s), ec);
 #endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+    }
   }
 
   if (result == 0)

+ 1 - 0
tests/lettuce/configurations/multi_instance/multi_auth.config.orig

@@ -0,0 +1 @@
+{"version": 2, "Auth": {"listen_on": [{"port": 47806, "address": "0.0.0.0"}]}, "Boss": {"components": {"b10-auth-2": {"kind": "needed", "special": "auth"}, "b10-auth": {"kind": "needed", "special": "auth"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}

+ 35 - 0
tests/lettuce/features/multi_instance.feature

@@ -0,0 +1,35 @@
+Feature: Multiple instances
+    This feature tests whether multiple instances can be run, and whether
+    removing them does not affect the running of other instances
+
+    Scenario: Multiple instances of Auth
+        # This config should have two running instances
+        Given I have bind10 running with configuration multi_instance/multi_auth.config
+        And bind10 module Auth should be running
+        A query for example.com should have rcode REFUSED
+
+        # this also checks whether the process is running
+        If I remember the pid of process b10-auth
+        And remember the pid of process b10-auth-2
+
+        When I remove bind10 configuration Boss/components value b10-auth-2
+
+        Then the pid of process b10-auth should not have changed
+        And a query for example.com should have rcode REFUSED
+
+        When I send bind10 the following commands
+        """
+        config add Boss/components b10-auth-2
+        config set Boss/components/b10-auth-2/special auth
+        config set Boss/components/b10-auth-2/kind needed
+        config commit
+        """
+        And wait for new bind10 stderr message AUTH_SERVER_STARTED
+        And remember the pid of process b10-auth-2
+
+        Then the pid of process b10-auth should not have changed
+        A query for example.com should have rcode REFUSED
+
+        When I remove bind10 configuration Boss/components value b10-auth
+        Then the pid of process b10-auth-2 should not have changed
+        A query for example.com should have rcode REFUSED

+ 92 - 0
tests/lettuce/features/terrain/bind10_control.py

@@ -16,6 +16,7 @@
 from lettuce import *
 import subprocess
 import re
+import json
 
 @step('start bind10(?: with configuration (\S+))?' +\
       '(?: with cmdctl port (\d+))?' +\
@@ -167,6 +168,83 @@ def check_bindctl_output(step, stderr, notv, string):
                           "' was found in bindctl output:\n" +\
                           output
 
+def parse_bindctl_output_as_data_structure():
+    """Helper function for data-related command tests: evaluates the
+       last output of bindctl as a data structure that can then be
+       inspected.
+       If the bindctl output is not valid (json) data, this call will
+       fail with an assertion failure.
+       If it is valid, it is parsed and returned as whatever data
+       structure it represented.
+    """
+    # strip any extra output after a charater that commonly terminates a valid
+    # JSON expression, i.e., ']', '}' and '"'.  (The extra output would
+    # contain 'Exit from bindctl' message, and depending on environment some
+    # other control-like characters...but why is this message even there?)
+    # Note that this filter is not perfect.  For example, it cannot recognize
+    # a simple expression of true/false/null.
+    output = re.sub("(.*)([^]}\"]*$)", r"\1", world.last_bindctl_stdout)
+    try:
+        return json.loads(output)
+    except ValueError as ve:
+        assert False, "Last bindctl output does not appear to be a " +\
+                      "parseable data structure: '" + output + "': " + str(ve)
+
+def find_process_pid(step, process_name):
+    """Helper function to request the running processes from Boss, and
+       return the pid of the process with the given process_name.
+       Fails with an assert if the response from boss is not valid JSON,
+       or if the process with the given name is not found.
+    """
+    # show_processes output is a list of lists, where the inner lists
+    # are of the form [ pid, "name" ]
+    # Not checking data form; errors will show anyway (if these turn
+    # out to be too vague, we can change this)
+    step.given('send bind10 the command Boss show_processes')
+    running_processes = parse_bindctl_output_as_data_structure()
+
+    for process in running_processes:
+        if process[1] == process_name:
+            return process[0]
+    assert False, "Process named " + process_name +\
+                  " not found in output of Boss show_processes";
+
+@step("remember the pid of process ([\S]+)")
+def remember_pid(step, process_name):
+    """Stores the PID of the process with the given name as returned by
+       Boss show_processes command.
+       Fails if the process with the given name does not appear to exist.
+       Stores the component_name->pid value in the dict world.process_pids.
+       This should only be used by the related step
+       'the pid of process <name> should (not) have changed'
+       Arguments:
+       process name ('process <name>') the name of the component to store
+                                       the pid of.
+    """
+    if world.process_pids is None:
+        world.process_pids = {}
+    world.process_pids[process_name] = find_process_pid(step, process_name)
+
+@step('pid of process ([\S]+) should not have changed')
+def check_pid(step, process_name):
+    """Checks the PID of the process with the given name as returned by
+       Boss show_processes command.
+       Fails if the process with the given name does not appear to exist.
+       Fails if the process with the given name exists, but has a different
+       pid than it had when the step 'remember the pid of process' was
+       called.
+       Fails if that step has not been called (since world.process_pids
+       does not exist).
+    """
+    assert world.process_pids is not None, "No process pids stored"
+    assert process_name in world.process_pids, "Process named " +\
+                                               process_name +\
+                                               " was not stored"
+    pid = find_process_pid(step, process_name)
+    assert world.process_pids[process_name] == pid,\
+                   "Expected pid: " + str(world.process_pids[process_name]) +\
+                   " Got pid: " + str(pid)
+
 @step('set bind10 configuration (\S+) to (.*)(?: with cmdctl port (\d+))?')
 def config_set_command(step, name, value, cmdctl_port):
     """
@@ -183,6 +261,20 @@ def config_set_command(step, name, value, cmdctl_port):
                 "quit"]
     run_bindctl(commands, cmdctl_port)
 
+@step('send bind10 the following commands(?: with cmdctl port (\d+))?')
+def send_multiple_commands(step, cmdctl_port):
+    """
+    Run bindctl, and send it the given multiline set of commands.
+    A quit command is always appended.
+    cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+                the command to. Defaults to 47805.
+    Fails if cmdctl does not exit with status code 0.
+    """
+    commands = step.multiline.split("\n")
+    # Always add quit
+    commands.append("quit")
+    run_bindctl(commands, cmdctl_port)
+
 @step('remove bind10 configuration (\S+)(?: value (\S+))?(?: with cmdctl port (\d+))?')
 def config_remove_command(step, name, value, cmdctl_port):
     """

+ 7 - 1
tests/lettuce/features/terrain/terrain.py

@@ -45,7 +45,9 @@ copylist = [
     ["configurations/example.org.config.orig",
      "configurations/example.org.config"],
     ["configurations/resolver/resolver_basic.config.orig",
-     "configurations/resolver/resolver_basic.config"]
+     "configurations/resolver/resolver_basic.config"],
+    ["configurations/multi_instance/multi_auth.config.orig",
+     "configurations/multi_instance/multi_auth.config"]
 ]
 
 # This is a list of files that, if present, will be removed before a scenario
@@ -343,6 +345,10 @@ def initialize(scenario):
     # Convenience variable to access the last query result from querying.py
     world.last_query_result = None
 
+    # For slightly better errors, initialize a process_pids for the relevant
+    # steps
+    world.process_pids = None
+
     # Some tests can modify the settings. If the tests fail half-way, or
     # don't clean up, this can leave configurations or data in a bad state,
     # so we copy them from originals before each scenario