diff --git a/CHANGES b/CHANGES index cffff9792c..21f34f63d8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +3150. [func] Improved startup and reconfiguration time by + enabling zones to load in multiple threads. [RT #25333] + +3149. [placeholder] + 3148. [bug] Processing of normal queries could be stalled when forwarding a UPDATE message. [RT #24711] diff --git a/bin/named/server.c b/bin/named/server.c index 9361c97f38..d724fd13e4 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.c,v 1.617 2011/08/30 05:16:10 marka Exp $ */ +/* $Id: server.c,v 1.618 2011/09/02 21:15:30 each Exp $ */ /*! \file */ @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -215,6 +216,16 @@ struct cfg_context { cfg_aclconfctx_t * actx; }; +/*% + * Holds state information for the initial zone loading process. + * Uses the isc_refcount structure to count the number of views + * with pending zone loads, dereferencing as each view finishes. + */ +typedef struct { + ns_server_t *server; + isc_refcount_t refs; +} ns_zoneload_t; + /* * These zones should not leak onto the Internet. */ @@ -5216,34 +5227,87 @@ load_configuration(const char *filename, ns_server_t *server, } static isc_result_t -load_zones(ns_server_t *server, isc_boolean_t stop) { +view_loaded(void *arg) { isc_result_t result; - dns_view_t *view; + ns_zoneload_t *zl = (ns_zoneload_t *) arg; + ns_server_t *server = zl->server; + unsigned int refs; - result = isc_task_beginexclusive(server->task); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - - /* - * Load zone data from disk. - */ - for (view = ISC_LIST_HEAD(server->viewlist); - view != NULL; - view = ISC_LIST_NEXT(view, link)) - { - CHECK(dns_view_load(view, stop)); - if (view->managed_keys != NULL) - CHECK(dns_zone_load(view->managed_keys)); - if (view->redirect != NULL) - CHECK(dns_zone_load(view->redirect)); - } /* * Force zone maintenance. Do this after loading * so that we know when we need to force AXFR of * slave zones whose master files are missing. + * + * We use the zoneload reference counter to let us + * know when all views are finished. */ - CHECK(dns_zonemgr_forcemaint(server->zonemgr)); + isc_refcount_decrement(&zl->refs, &refs); + if (refs != 0) + return (ISC_R_SUCCESS); + + isc_refcount_destroy(&zl->refs); + isc_mem_put(server->mctx, zl, sizeof (*zl)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_NOTICE, "all zones loaded"); + CHECKFATAL(dns_zonemgr_forcemaint(server->zonemgr), + "forcing zone maintenance"); + + ns_os_started(); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_NOTICE, "running"); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_zones(ns_server_t *server) { + isc_result_t result; + dns_view_t *view; + ns_zoneload_t *zl; + unsigned int refs = 0; + + zl = isc_mem_get(server->mctx, sizeof (*zl)); + if (zl == NULL) + return (ISC_R_NOMEMORY); + zl->server = server; + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_refcount_init(&zl->refs, 1); + + /* + * Schedule zones to be loaded from disk. + */ + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (view->managed_keys != NULL) + CHECK(dns_zone_load(view->managed_keys)); + if (view->redirect != NULL) + CHECK(dns_zone_load(view->redirect)); + isc_refcount_increment(&zl->refs, NULL); + CHECK(dns_view_asyncload(view, view_loaded, zl)); + } + cleanup: + isc_refcount_decrement(&zl->refs, &refs); + if (result != ISC_R_SUCCESS || refs == 0) { + isc_refcount_destroy(&zl->refs); + isc_mem_put(server->mctx, zl, sizeof (*zl)); + } else { + /* + * Place the task manager into privileged mode. This + * ensures that after we leave task-exclusive mode, no + * other tasks will be able to run except for the ones + * that are loading zones. + */ + isc_taskmgr_setmode(ns_g_taskmgr, isc_taskmgrmode_privileged); + } + isc_task_endexclusive(server->task); return (result); } @@ -5331,11 +5395,7 @@ run_server(isc_task_t *task, isc_event_t *event) { isc_hash_init(); - CHECKFATAL(load_zones(server, ISC_FALSE), "loading zones"); - - ns_os_started(); - isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, - ISC_LOG_NOTICE, "running"); + CHECKFATAL(load_zones(server), "loading zones"); } void @@ -5770,7 +5830,7 @@ reload(ns_server_t *server) { isc_result_t result; CHECK(loadconfig(server)); - result = load_zones(server, ISC_FALSE); + result = load_zones(server); if (result == ISC_R_SUCCESS) isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_INFO, diff --git a/bin/tests/startperf/README b/bin/tests/startperf/README index 0f350c2ecf..0b8185b9b2 100644 --- a/bin/tests/startperf/README +++ b/bin/tests/startperf/README @@ -1,9 +1,17 @@ These scripts generate a named.conf file with an arbitrary number of small zones, for testing startup performance. -To generate a test server with 1000 zones, run: +To generate a test server with 1000 zones each of which contains 5 A +records, run: - $ sh setup.sh 1000 > named.conf + $ sh setup.sh 1000 5 > named.conf -Zones are generated with random names, and all of them load from the same -file: smallzone.db. +Zones are generated with random names, and the zone files are created +in the subdirectory "zones". + +Or, to generate a test server with 100 zones which all load from the same +generic file (smallzone.db): + + $ sh setup.sh -s 100 > named.conf + +The "number of records" argument is ignored if -s is used. diff --git a/bin/tests/startperf/clean.sh b/bin/tests/startperf/clean.sh new file mode 100644 index 0000000000..8732ddb504 --- /dev/null +++ b/bin/tests/startperf/clean.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rm -rf zones +rm -f named.conf diff --git a/bin/tests/startperf/makenames.pl b/bin/tests/startperf/makenames.pl index e43e7d03a3..867a075d38 100644 --- a/bin/tests/startperf/makenames.pl +++ b/bin/tests/startperf/makenames.pl @@ -14,17 +14,19 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: makenames.pl,v 1.2 2011/07/06 05:05:51 each Exp $ +# $Id: makenames.pl,v 1.3 2011/09/02 21:15:35 each Exp $ use strict; -die "Usage: makenames.pl " if (@ARGV == 0); +die "Usage: makenames.pl []" if (@ARGV == 0 || @ARGV > 2); +my $len = 10; +$len = @ARGV[1] if (@ARGV == 2); my @chars = split("", "abcdefghijklmnopqrstuvwxyz123456789"); srand; for (my $i = 0; $i < @ARGV[0]; $i++) { my $name = ""; - for (my $j = 0; $j < 10; $j++) { + for (my $j = 0; $j < $len; $j++) { my $r = rand 35; $name .= $chars[$r]; } diff --git a/bin/tests/startperf/mkzonefile.pl b/bin/tests/startperf/mkzonefile.pl new file mode 100644 index 0000000000..b76038fe8e --- /dev/null +++ b/bin/tests/startperf/mkzonefile.pl @@ -0,0 +1,51 @@ +#!/usr/bin/perl +# +# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: mkzonefile.pl,v 1.2 2011/09/02 21:15:35 each Exp $ +use strict; + +die "Usage: makenames.pl zonename num_records" if (@ARGV != 2); +my $zname = @ARGV[0]; +my $nrecords = @ARGV[1]; + +my @chars = split("", "abcdefghijklmnopqrstuvwxyz"); + +print"\$TTL 300 ; 5 minutes +\$ORIGIN $zname. +@ IN SOA mname1. . ( + 2011080201 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3\n"; + +srand; +for (my $i = 0; $i < $nrecords; $i++) { + my $name = ""; + for (my $j = 0; $j < 8; $j++) { + my $r = rand 25; + $name .= $chars[$r]; + } + print "$name" . "\tIN\tA\t"; + my $x = int rand 254; + my $y = int rand 254; + my $z = int rand 254; + print "10.$x.$y.$z\n"; +} + diff --git a/bin/tests/startperf/setup.sh b/bin/tests/startperf/setup.sh index da1cf03d37..717e047d91 100644 --- a/bin/tests/startperf/setup.sh +++ b/bin/tests/startperf/setup.sh @@ -14,13 +14,30 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: setup.sh,v 1.3 2011/07/07 23:47:49 tbox Exp $ +# $Id: setup.sh,v 1.4 2011/09/02 21:15:35 each Exp $ -if [ "$#" -ne 1 ]; then - echo "Usage: $0 " +usage () { + echo "Usage: $0 [-s] []" + echo " -s: use the same zone file all zones" exit 1 +} + +if [ "$#" -lt 1 -o "$#" -gt 3 ]; then + usage fi +single_file="" +if [ $1 = "-s" ]; then + single_file=yes + shift +fi + +nzones=$1 +shift + +nrecords=5 +[ "$#" -eq 1 ] && nrecords=$1 + . ../system/conf.sh cat << EOF @@ -59,6 +76,12 @@ logging { EOF -$PERL makenames.pl $1 | while read zonename; do +$PERL makenames.pl $nzones | while read zonename; do + if [ $single_file ]; then echo "zone $zonename { type master; file \"smallzone.db\"; };" + else + [ -d zones ] || mkdir zones + $PERL mkzonefile.pl $zonename $nrecords > zones/$zonename.db + echo "zone $zonename { type master; file \"zones/$zonename.db\"; };" + fi done diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h index 563031a9d4..723f9ca275 100644 --- a/lib/dns/include/dns/events.h +++ b/lib/dns/include/dns/events.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: events.h,v 1.58 2011/08/30 23:46:53 tbox Exp $ */ +/* $Id: events.h,v 1.59 2011/09/02 21:15:36 each Exp $ */ #ifndef DNS_EVENTS_H #define DNS_EVENTS_H 1 @@ -76,6 +76,7 @@ #define DNS_EVENT_ADBGROWNAMES (ISC_EVENTCLASS_DNS + 46) #define DNS_EVENT_ZONESECURESERIAL (ISC_EVENTCLASS_DNS + 47) #define DNS_EVENT_ZONESECUREDB (ISC_EVENTCLASS_DNS + 48) +#define DNS_EVENT_ZONELOAD (ISC_EVENTCLASS_DNS + 49) #define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0) #define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535) diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 248835569e..720499fa61 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: view.h,v 1.134 2011/08/02 20:36:13 each Exp $ */ +/* $Id: view.h,v 1.135 2011/09/02 21:15:36 each Exp $ */ #ifndef DNS_VIEW_H #define DNS_VIEW_H 1 @@ -76,6 +76,7 @@ #include #include #include +#include ISC_LANG_BEGINDECLS @@ -728,14 +729,21 @@ dns_view_load(dns_view_t *view, isc_boolean_t stop); isc_result_t dns_view_loadnew(dns_view_t *view, isc_boolean_t stop); + +isc_result_t +dns_view_asyncload(dns_view_t *view, dns_zt_allloaded_t callback, void *arg); /*%< * Load zones attached to this view. dns_view_load() loads * all zones whose master file has changed since the last * load; dns_view_loadnew() loads only zones that have never * been loaded. * + * dns_view_asyncload() loads zones asynchronously. When all zones + * in the view have finished loading, 'callback' is called with argument + * 'arg' to inform the caller. + * * If 'stop' is ISC_TRUE, stop on the first error and return it. - * If 'stop' is ISC_FALSE, ignore errors. + * If 'stop' is ISC_FALSE (or we are loading asynchronously), ignore errors. * * Requires: * diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 1808f3605e..1e2ccc4fc3 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zone.h,v 1.192 2011/08/30 05:16:15 marka Exp $ */ +/* $Id: zone.h,v 1.193 2011/09/02 21:15:36 each Exp $ */ #ifndef DNS_ZONE_H #define DNS_ZONE_H 1 @@ -35,6 +35,7 @@ #include #include #include +#include typedef enum { dns_zone_none, @@ -287,6 +288,7 @@ dns_zone_loadnew(dns_zone_t *zone); isc_result_t dns_zone_loadandthaw(dns_zone_t *zone); + /*%< * Cause the database to be loaded from its backing store. * Confirm that the minimum requirements for the zone type are @@ -311,6 +313,25 @@ dns_zone_loadandthaw(dns_zone_t *zone); *\li Any result value from dns_db_load(). */ +isc_result_t +dns_zone_asyncload(dns_zone_t *zone, dns_zt_zoneloaded_t done, void *arg); +/*%< + * Cause the database to be loaded from its backing store asynchronously. + * Other zone maintenance functions are suspended until this is complete. + * When finished, 'done' is called to inform the caller, with 'arg' as + * its first argument and 'zone' as its second. (Normally, 'arg' is + * expected to point to the zone table but is left undefined for testing + * purposes.) + */ + +isc_boolean_t +dns__zone_loadpending(dns_zone_t *zone); +/*%< + * Indicates whether the zone is waiting to be loaded asynchronously. + * (Not currently intended for use outside of this module and associated + * tests.) + */ + void dns_zone_attach(dns_zone_t *source, dns_zone_t **target); /*%< @@ -1427,6 +1448,14 @@ dns_zonemgr_forcemaint(dns_zonemgr_t *zmgr); * earliest convenience. */ +void +dns__zonemgr_run(isc_task_t *task, isc_event_t *event); +/*%< + * Event handler to call dns_zonemgr_forcemaint(); used to start + * zone operations from a unit test. Not intended for use outside + * libdns or related tests. + */ + void dns_zonemgr_resumexfrs(dns_zonemgr_t *zmgr); /*%< diff --git a/lib/dns/include/dns/zt.h b/lib/dns/include/dns/zt.h index 6cfe3d3fb1..49bb561f0a 100644 --- a/lib/dns/include/dns/zt.h +++ b/lib/dns/include/dns/zt.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zt.h,v 1.38 2007/06/19 23:47:17 tbox Exp $ */ +/* $Id: zt.h,v 1.39 2011/09/02 21:15:37 each Exp $ */ #ifndef DNS_ZT_H #define DNS_ZT_H 1 @@ -30,6 +30,21 @@ ISC_LANG_BEGINDECLS +typedef isc_result_t +(*dns_zt_allloaded_t)(void *arg); +/*%< + * Method prototype: when all pending zone loads are complete, + * the zone table can inform the caller via a callback function with + * this signature. + */ + +typedef isc_result_t +(*dns_zt_zoneloaded_t)(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task); +/*%< + * Method prototype: when a zone finishes loading, the zt object + * can be informed via a callback function with this signature. + */ + isc_result_t dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **zt); /*%< @@ -134,6 +149,9 @@ dns_zt_load(dns_zt_t *zt, isc_boolean_t stop); isc_result_t dns_zt_loadnew(dns_zt_t *zt, isc_boolean_t stop); + +isc_result_t +dns_zt_asyncload(dns_zt_t *zt, dns_zt_allloaded_t alldone, void *arg); /*%< * Load all zones in the table. If 'stop' is ISC_TRUE, * stop on the first error and return it. If 'stop' @@ -142,6 +160,10 @@ dns_zt_loadnew(dns_zt_t *zt, isc_boolean_t stop); * dns_zt_loadnew() only loads zones that are not yet loaded. * dns_zt_load() also loads zones that are already loaded and * and whose master file has changed since the last load. + * dns_zt_asyncload() loads zones asynchronously; when all + * zones in the zone table have finished loaded (or failed due + * to errors), the caller is informed by calling 'alldone' + * with an argument of 'arg'. * * Requires: * \li 'zt' to be valid @@ -178,6 +200,16 @@ dns_zt_apply2(dns_zt_t *zt, isc_boolean_t stop, isc_result_t *sub, * any error code from 'action'. */ +isc_boolean_t +dns_zt_loadspending(dns_zt_t *zt); +/*%< + * Returns ISC_TRUE if and only if there are zones still waiting to + * be loaded in zone table 'zt'. + * + * Requires: + * \li 'zt' to be valid. + */ + ISC_LANG_ENDDECLS #endif /* DNS_ZT_H */ diff --git a/lib/dns/tests/Makefile.in b/lib/dns/tests/Makefile.in index b8bd9dc128..420457db98 100644 --- a/lib/dns/tests/Makefile.in +++ b/lib/dns/tests/Makefile.in @@ -12,7 +12,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: Makefile.in,v 1.7 2011/08/23 01:29:38 each Exp $ +# $Id: Makefile.in,v 1.8 2011/09/02 21:15:37 each Exp $ srcdir = @srcdir@ VPATH = @srcdir@ @@ -37,12 +37,12 @@ DNSDEPLIBS = ../libdns.@A@ LIBS = @LIBS@ @ATFLIBS@ OBJS = dnstest.@O@ -SRCS = dnstest.c master_test.c time_test.c update_test.c \ - zonemgr_test.c dbiterator_test.c +SRCS = dnstest.c master_test.c dbiterator_test.c time_test.c \ + update_test.c zonemgr_test.c zt_test.c SUBDIRS = -TARGETS = master_test@EXEEXT@ time_test@EXEEXT@ update_test@EXEEXT@ \ - zonemgr_test@EXEEXT@ dbiterator_test@EXEEXT@ +TARGETS = master_test@EXEEXT@ dbiterator_test@EXEEXT@ time_test@EXEEXT@ \ + update_test@EXEEXT@ zonemgr_test@EXEEXT@ zt_test@EXEEXT@ @BIND9_MAKE_RULES@ @@ -71,6 +71,11 @@ dbiterator_test@EXEEXT@: dbiterator_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPL dbiterator_test.@O@ dnstest.@O@ ${DNSLIBS} \ ${ISCLIBS} ${LIBS} +zt_test@EXEEXT@: zt_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + zt_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + unit:: sh ${top_srcdir}/unit/unittest.sh diff --git a/lib/dns/tests/dnstest.c b/lib/dns/tests/dnstest.c index b86412a50b..a0b713be9b 100644 --- a/lib/dns/tests/dnstest.c +++ b/lib/dns/tests/dnstest.c @@ -14,12 +14,14 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: dnstest.c,v 1.4 2011/07/06 01:36:32 each Exp $ */ +/* $Id: dnstest.c,v 1.5 2011/09/02 21:15:37 each Exp $ */ /*! \file */ #include +#include + #include #include #include @@ -32,9 +34,12 @@ #include #include +#include #include #include #include +#include +#include #include @@ -44,8 +49,11 @@ isc_mem_t *mctx = NULL; isc_entropy_t *ectx = NULL; isc_log_t *lctx = NULL; isc_taskmgr_t *taskmgr = NULL; +isc_task_t *maintask = NULL; isc_timermgr_t *timermgr = NULL; isc_socketmgr_t *socketmgr = NULL; +dns_zonemgr_t *zonemgr = NULL; +isc_boolean_t app_running = ISC_FALSE; int ncpus; static isc_boolean_t hash_active = ISC_FALSE, dst_active = ISC_FALSE; @@ -67,8 +75,12 @@ static isc_logcategory_t categories[] = { static void cleanup_managers() { + if (app_running) + isc_app_finish(); if (socketmgr != NULL) isc_socketmgr_destroy(&socketmgr); + if (maintask != NULL) + isc_task_destroy(&maintask); if (taskmgr != NULL) isc_taskmgr_destroy(&taskmgr); if (timermgr != NULL) @@ -87,6 +99,8 @@ create_managers() { CHECK(isc_taskmgr_create(mctx, ncpus, 0, &taskmgr)); CHECK(isc_timermgr_create(mctx, &timermgr)); CHECK(isc_socketmgr_create(mctx, &socketmgr)); + CHECK(isc_task_create(taskmgr, 0, &maintask)); + CHECK(isc_app_start()); return (ISC_R_SUCCESS); cleanup: @@ -134,6 +148,14 @@ dns_test_begin(FILE *logfile, isc_boolean_t start_managers) { if (start_managers) CHECK(create_managers()); + /* + * atf-run changes us to a /tmp directory, so tests + * that access test data files must first chdir to the proper + * location. + */ + if (chdir(TESTS) == -1) + CHECK(ISC_R_FAILURE); + return (ISC_R_SUCCESS); cleanup: @@ -162,3 +184,114 @@ dns_test_end() { isc_mem_destroy(&mctx); } +/* + * Create a zone with origin 'name', return a pointer to the zone object in + * 'zonep'. If 'view' is set, add the zone to that view; otherwise, create + * a new view for the purpose. + * + * If the created view is going to be needed by the caller subsequently, + * then 'keepview' should be set to true; this will prevent the view + * from being detached. In this case, the caller is responsible for + * detaching the view. + */ +isc_result_t +dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, + isc_boolean_t keepview) +{ + isc_result_t result; + dns_zone_t *zone = NULL; + isc_buffer_t buffer; + dns_fixedname_t fixorigin; + dns_name_t *origin; + + if (view == NULL) + CHECK(dns_view_create(mctx, dns_rdataclass_in, "view", &view)); + else if (!keepview) + keepview = ISC_TRUE; + + CHECK(dns_zone_create(&zone, mctx)); + + isc_buffer_init(&buffer, name, strlen(name)); + isc_buffer_add(&buffer, strlen(name)); + dns_fixedname_init(&fixorigin); + origin = dns_fixedname_name(&fixorigin); + CHECK(dns_name_fromtext(origin, &buffer, dns_rootname, 0, NULL)); + CHECK(dns_zone_setorigin(zone, origin)); + dns_zone_setview(zone, view); + dns_zone_settype(zone, dns_zone_master); + dns_zone_setclass(zone, view->rdclass); + dns_view_addzone(view, zone); + + if (!keepview) + dns_view_detach(&view); + + *zonep = zone; + + return (ISC_R_SUCCESS); + + cleanup: + if (zone != NULL) + dns_zone_detach(&zone); + if (view != NULL) + dns_view_detach(&view); + return (result); +} + +isc_result_t +dns_test_setupzonemgr() { + isc_result_t result; + REQUIRE(zonemgr == NULL); + + result = dns_zonemgr_create(mctx, taskmgr, timermgr, socketmgr, + &zonemgr); + return (result); +} + +isc_result_t +dns_test_managezone(dns_zone_t *zone) { + isc_result_t result; + REQUIRE(zonemgr != NULL); + + result = dns_zonemgr_setsize(zonemgr, 1); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_zonemgr_managezone(zonemgr, zone); + return (result); +} + +void +dns_test_releasezone(dns_zone_t *zone) { + REQUIRE(zonemgr != NULL); + dns_zonemgr_releasezone(zonemgr, zone); +} + +void +dns_test_closezonemgr() { + REQUIRE(zonemgr != NULL); + + dns_zonemgr_shutdown(zonemgr); + dns_zonemgr_detach(&zonemgr); +} + +/* + * Sleep for 'usec' microseconds. + */ +void +dns_test_nap(isc_uint32_t usec) { +#ifdef HAVE_NANOSLEEP + struct timespec ts; + + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + nanosleep(&ts, NULL); +#elif HAVE_USLEEP + usleep(usec); +#else + /* + * No fractional-second sleep function is available, so we + * round up to the nearest second and sleep instead + */ + sleep((usec / 1000000) + 1); +#endif +} diff --git a/lib/dns/tests/dnstest.h b/lib/dns/tests/dnstest.h index 94d377ba7c..4d8229e381 100644 --- a/lib/dns/tests/dnstest.h +++ b/lib/dns/tests/dnstest.h @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: dnstest.h,v 1.3 2011/07/06 01:36:32 each Exp $ */ +/* $Id: dnstest.h,v 1.4 2011/09/02 21:15:37 each Exp $ */ /*! \file */ @@ -31,6 +31,7 @@ #include #include +#include #define CHECK(r) \ do { \ @@ -42,11 +43,13 @@ extern isc_mem_t *mctx; extern isc_entropy_t *ectx; extern isc_log_t *lctx; -isc_taskmgr_t *taskmgr; -isc_timermgr_t *timermgr; -isc_socketmgr_t *socketmgr; -int ncpus; - +extern isc_taskmgr_t *taskmgr; +extern isc_task_t *maintask; +extern isc_timermgr_t *timermgr; +extern isc_socketmgr_t *socketmgr; +extern dns_zonemgr_t *zonemgr; +extern isc_boolean_t app_running; +extern int ncpus; isc_result_t dns_test_begin(FILE *logfile, isc_boolean_t create_managers); @@ -54,3 +57,21 @@ dns_test_begin(FILE *logfile, isc_boolean_t create_managers); void dns_test_end(void); +isc_result_t +dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, + isc_boolean_t keepview); + +isc_result_t +dns_test_setupzonemgr(void); + +isc_result_t +dns_test_managezone(dns_zone_t *zone); + +void +dns_test_releasezone(dns_zone_t *zone); + +void +dns_test_closezonemgr(void); + +void +dns_test_nap(isc_uint32_t usec); diff --git a/lib/dns/tests/master_test.c b/lib/dns/tests/master_test.c index 06aabd07bc..baee4e1ef1 100644 --- a/lib/dns/tests/master_test.c +++ b/lib/dns/tests/master_test.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: master_test.c,v 1.4 2011/07/06 01:36:32 each Exp $ */ +/* $Id: master_test.c,v 1.5 2011/09/02 21:15:37 each Exp $ */ /*! \file */ @@ -85,14 +85,6 @@ test_master(const char *testfile) { dns_rdatacallbacks_init_stdio(&callbacks); callbacks.add = add_callback; - /* - * atf-run changes us to a /tmp directory, so tests - * that access test data files must first chdir to the proper - * location. - */ - if (chdir(TESTS) == -1) - return (ISC_R_FAILURE); - result = dns_master_loadfile(testfile, &dns_origin, &dns_origin, dns_rdataclass_in, ISC_TRUE, &callbacks, mctx); diff --git a/lib/dns/tests/testdata/zt/zone1.db b/lib/dns/tests/testdata/zt/zone1.db new file mode 100644 index 0000000000..16fe069b6c --- /dev/null +++ b/lib/dns/tests/testdata/zt/zone1.db @@ -0,0 +1,11 @@ +$TTL 1000 +@ in soa localhost. postmaster.localhost. ( + 1993050801 ;serial + 3600 ;refresh + 1800 ;retry + 604800 ;expiration + 3600 ) ;minimum + in ns ns.vix.com. + in ns ns2.vix.com. + in ns ns3.vix.com. +a in a 1.2.3.4 diff --git a/lib/dns/tests/zonemgr_test.c b/lib/dns/tests/zonemgr_test.c index edb0af3332..f9ee19e070 100644 --- a/lib/dns/tests/zonemgr_test.c +++ b/lib/dns/tests/zonemgr_test.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zonemgr_test.c,v 1.2 2011/07/06 05:05:51 each Exp $ */ +/* $Id: zonemgr_test.c,v 1.3 2011/09/02 21:15:37 each Exp $ */ /*! \file */ @@ -34,41 +34,6 @@ #include "dnstest.h" -static isc_result_t -make_zone(const char *name, dns_zone_t **zonep) { - isc_result_t result; - dns_view_t *view = NULL; - dns_zone_t *zone = NULL; - isc_buffer_t buffer; - dns_fixedname_t fixorigin; - dns_name_t *origin; - - CHECK(dns_view_create(mctx, dns_rdataclass_in, "view", &view)); - CHECK(dns_zone_create(&zone, mctx)); - - isc_buffer_init(&buffer, name, strlen(name)); - isc_buffer_add(&buffer, strlen(name)); - dns_fixedname_init(&fixorigin); - origin = dns_fixedname_name(&fixorigin); - CHECK(dns_name_fromtext(origin, &buffer, dns_rootname, 0, NULL)); - CHECK(dns_zone_setorigin(zone, origin)); - dns_zone_setview(zone, view); - dns_zone_settype(zone, dns_zone_master); - dns_zone_setclass(zone, view->rdclass); - - dns_view_detach(&view); - *zonep = zone; - - return (ISC_R_SUCCESS); - - cleanup: - if (zone != NULL) - dns_zone_detach(&zone); - if (view != NULL) - dns_view_detach(&view); - return (result); -} - /* * Individual unit tests */ @@ -115,7 +80,7 @@ ATF_TC_BODY(zonemgr_managezone, tc) { &zonemgr); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); - result = make_zone("foo", &zone); + result = dns_test_makezone("foo", &zone, NULL, ISC_FALSE); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); /* This should not succeed until the dns_zonemgr_setsize() is run */ diff --git a/lib/dns/tests/zt_test.c b/lib/dns/tests/zt_test.c new file mode 100644 index 0000000000..c1011edb45 --- /dev/null +++ b/lib/dns/tests/zt_test.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: zt_test.c,v 1.2 2011/09/02 21:15:37 each Exp $ */ + +/*! \file */ + +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dnstest.h" + +/* + * Helper functions + */ +static isc_result_t +count_zone(dns_zone_t *zone, void *uap) { + int *nzones = (int *)uap; + + UNUSED(zone); + + *nzones += 1; + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_done(dns_zt_t *zt, dns_zone_t *zone) { + /* We treat zt as a pointer to a boolean for testing purposes */ + isc_boolean_t *done = (isc_boolean_t *) zt; + + UNUSED(zone); + + *done = ISC_TRUE; + isc_app_shutdown(); + return (ISC_R_SUCCESS); +} + +static isc_result_t +all_done(void *arg) { + isc_boolean_t *done = (isc_boolean_t *) arg; + + *done = ISC_TRUE; + isc_app_shutdown(); + return (ISC_R_SUCCESS); +} + +/* + * Individual unit tests + */ +ATF_TC(apply); +ATF_TC_HEAD(apply, tc) { + atf_tc_set_md_var(tc, "descr", "apply a function to a zone table"); +} +ATF_TC_BODY(apply, tc) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_view_t *view = NULL; + int nzones = 0; + + UNUSED(tc); + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_makezone("foo", &zone, NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + view = dns_zone_getview(zone); + ATF_REQUIRE(view->zonetable != NULL); + + ATF_CHECK_EQ(0, nzones); + result = dns_zt_apply(view->zonetable, ISC_FALSE, count_zone, &nzones); + ATF_CHECK_EQ(1, nzones); + + /* These steps are necessary so the zone can be detached properly */ + result = dns_test_setupzonemgr(); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_test_managezone(zone); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_test_releasezone(zone); + dns_test_closezonemgr(); + + /* The view was left attached in dns_test_makezone() */ + dns_view_detach(&view); + dns_zone_detach(&zone); + + dns_test_end(); +} + +ATF_TC(asyncload_zone); +ATF_TC_HEAD(asyncload_zone, tc) { + atf_tc_set_md_var(tc, "descr", "asynchronous zone load"); +} +ATF_TC_BODY(asyncload_zone, tc) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_view_t *view = NULL; + dns_db_t *db = NULL; + isc_boolean_t done = ISC_FALSE; + int i = 0; + + UNUSED(tc); + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_makezone("foo", &zone, NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_setupzonemgr(); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_test_managezone(zone); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + view = dns_zone_getview(zone); + ATF_REQUIRE(view->zonetable != NULL); + + ATF_CHECK(!dns__zone_loadpending(zone)); + ATF_CHECK(!done); + dns_zone_setfile(zone, "testdata/zt/zone1.db"); + dns_zone_asyncload(zone, load_done, (void *) &done); + ATF_CHECK(dns__zone_loadpending(zone)); + + isc_app_run(); + while (dns__zone_loadpending(zone) && i++ < 5000) + dns_test_nap(1000); + ATF_CHECK(done); + + /* The zone should now be loaded; test it */ + result = dns_zone_getdb(zone, &db); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(db != NULL); + if (db != NULL) + dns_db_detach(&db); + + dns_test_releasezone(zone); + dns_test_closezonemgr(); + + dns_zone_detach(&zone); + dns_view_detach(&view); + + dns_test_end(); +} + +ATF_TC(asyncload_zt); +ATF_TC_HEAD(asyncload_zt, tc) { + atf_tc_set_md_var(tc, "descr", "asynchronous zone table load"); +} +ATF_TC_BODY(asyncload_zt, tc) { + isc_result_t result; + dns_zone_t *zone1 = NULL, *zone2 = NULL, *zone3 = NULL; + dns_view_t *view; + dns_zt_t *zt; + dns_db_t *db = NULL; + isc_boolean_t done = ISC_FALSE; + int i = 0; + + UNUSED(tc); + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_makezone("foo", &zone1, NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_zone_setfile(zone1, "testdata/zt/zone1.db"); + view = dns_zone_getview(zone1); + + result = dns_test_makezone("bar", &zone2, view, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_zone_setfile(zone2, "testdata/zt/zone1.db"); + + /* This one will fail to load */ + result = dns_test_makezone("fake", &zone3, view, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_zone_setfile(zone3, "testdata/zt/nonexistent.db"); + + zt = view->zonetable; + ATF_REQUIRE(zt != NULL); + + result = dns_test_setupzonemgr(); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_test_managezone(zone1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_test_managezone(zone2); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_test_managezone(zone3); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_CHECK(!dns__zone_loadpending(zone1)); + ATF_CHECK(!dns__zone_loadpending(zone2)); + ATF_CHECK(!done); + dns_zt_asyncload(zt, all_done, (void *) &done); + + isc_app_run(); + while (!done && i++ < 5000) + dns_test_nap(1000); + ATF_CHECK(done); + + /* Both zones should now be loaded; test them */ + result = dns_zone_getdb(zone1, &db); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(db != NULL); + if (db != NULL) + dns_db_detach(&db); + + result = dns_zone_getdb(zone2, &db); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(db != NULL); + if (db != NULL) + dns_db_detach(&db); + + dns_test_releasezone(zone3); + dns_test_releasezone(zone2); + dns_test_releasezone(zone1); + dns_test_closezonemgr(); + + dns_zone_detach(&zone1); + dns_zone_detach(&zone2); + dns_zone_detach(&zone3); + dns_view_detach(&view); + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, apply); + ATF_TP_ADD_TC(tp, asyncload_zone); + ATF_TP_ADD_TC(tp, asyncload_zt); + return (atf_no_error()); +} diff --git a/lib/dns/view.c b/lib/dns/view.c index e61e91e414..695e48b08a 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: view.c,v 1.182 2011/08/30 21:14:50 marka Exp $ */ +/* $Id: view.c,v 1.183 2011/09/02 21:15:35 each Exp $ */ /*! \file */ @@ -1418,6 +1418,7 @@ isc_result_t dns_view_load(dns_view_t *view, isc_boolean_t stop) { REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->zonetable != NULL); return (dns_zt_load(view->zonetable, stop)); } @@ -1426,9 +1427,20 @@ isc_result_t dns_view_loadnew(dns_view_t *view, isc_boolean_t stop) { REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->zonetable != NULL); return (dns_zt_loadnew(view->zonetable, stop)); } + +isc_result_t +dns_view_asyncload(dns_view_t *view, dns_zt_allloaded_t callback, void *arg) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->zonetable != NULL); + + return (dns_zt_asyncload(view->zonetable, callback, arg)); +} + + #endif /* BIND9 */ isc_result_t diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 7f125fb525..05298a5d28 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zone.c,v 1.628 2011/08/31 06:49:10 marka Exp $ */ +/* $Id: zone.c,v 1.629 2011/09/02 21:15:36 each Exp $ */ /*! \file */ @@ -79,6 +79,7 @@ #include #include #include +#include #include @@ -146,6 +147,7 @@ typedef ISC_LIST(dns_signing_t) dns_signinglist_t; typedef struct dns_nsec3chain dns_nsec3chain_t; typedef ISC_LIST(dns_nsec3chain_t) dns_nsec3chainlist_t; typedef struct dns_keyfetch dns_keyfetch_t; +typedef struct dns_asyncload dns_asyncload_t; #define DNS_ZONE_CHECKLOCK #ifdef DNS_ZONE_CHECKLOCK @@ -245,6 +247,7 @@ struct dns_zone { unsigned int notifycnt; isc_sockaddr_t notifyfrom; isc_task_t *task; + isc_task_t *loadtask; isc_sockaddr_t notifysrc4; isc_sockaddr_t notifysrc6; isc_sockaddr_t xfrsource4; @@ -402,7 +405,7 @@ struct dns_zone { #define DNS_ZONEFLG_NEEDCOMPACT 0x02000000U #define DNS_ZONEFLG_REFRESHING 0x04000000U /*%< Refreshing keydata */ #define DNS_ZONEFLG_THAW 0x08000000U -/* #define DNS_ZONEFLG_XXXXX 0x10000000U XXXMPA unused. */ +#define DNS_ZONEFLG_LOADPENDING 0x10000000U /*%< Loading scheduled */ #define DNS_ZONEFLG_NODELAY 0x20000000U #define DNS_ZONE_OPTION(z,o) (((z)->options & (o)) != 0) @@ -436,6 +439,7 @@ struct dns_zonemgr { isc_timermgr_t * timermgr; isc_socketmgr_t * socketmgr; isc_taskpool_t * zonetasks; + isc_taskpool_t * loadtasks; isc_task_t * task; isc_ratelimiter_t * rl; isc_rwlock_t rwlock; @@ -593,6 +597,15 @@ struct dns_keyfetch { dns_fetch_t *fetch; }; +/*% + * Hold state for an asynchronous load + */ +struct dns_asyncload { + dns_zone_t *zone; + dns_zt_zoneloaded_t loaded; + void *loaded_arg; +}; + #define HOUR 3600 #define DAY (24*HOUR) #define MONTH (30*DAY) @@ -821,6 +834,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->notifytype = dns_notifytype_yes; zone->notifycnt = 0; zone->task = NULL; + zone->loadtask = NULL; zone->update_acl = NULL; zone->forward_acl = NULL; zone->notify_acl = NULL; @@ -932,6 +946,8 @@ zone_free(dns_zone_t *zone) { if (zone->task != NULL) isc_task_detach(&zone->task); + if (zone->loadtask != NULL) + isc_task_detach(&zone->loadtask); if (zone->zmgr != NULL) dns_zonemgr_releasezone(zone->zmgr, zone); @@ -1458,7 +1474,6 @@ zone_load(dns_zone_t *zone, unsigned int flags) { goto cleanup; } - INSIST(zone->db_argc >= 1); rbt = strcmp(zone->db_argv[0], "rbt") == 0 || @@ -1617,6 +1632,82 @@ dns_zone_loadnew(dns_zone_t *zone) { return (zone_load(zone, DNS_ZONELOADFLAG_NOSTAT)); } +static void +zone_asyncload(isc_task_t *task, isc_event_t *event) { + dns_asyncload_t *asl = event->ev_arg; + dns_zone_t *zone = asl->zone; + isc_result_t result = ISC_R_SUCCESS; + + UNUSED(task); + + REQUIRE(DNS_ZONE_VALID(zone)); + + if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) + result = ISC_R_CANCELED; + isc_event_free(&event); + if (result == ISC_R_CANCELED || + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING)) + return; + + result = zone_load(zone, 0); + + LOCK_ZONE(zone); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING); + UNLOCK_ZONE(zone); + + /* Inform the zone table we've finished loading */ + if (asl->loaded != NULL) + (asl->loaded)(asl->loaded_arg, zone, task); + + isc_mem_put(zone->mctx, asl, sizeof (*asl)); +} + +isc_result_t +dns_zone_asyncload(dns_zone_t *zone, dns_zt_zoneloaded_t done, void *arg) { + isc_event_t *e; + dns_asyncload_t *asl = NULL; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ZONE_VALID(zone)); + + if (zone->zmgr == NULL) + return (ISC_R_FAILURE); + + asl = isc_mem_get(zone->mctx, sizeof (*asl)); + if (asl == NULL) + CHECK(ISC_R_NOMEMORY); + + asl->zone = zone; + asl->loaded = done; + asl->loaded_arg = arg; + + e = isc_event_allocate(zone->zmgr->mctx, zone->zmgr, + DNS_EVENT_ZONELOAD, + zone_asyncload, asl, + sizeof(isc_event_t)); + if (e == NULL) + CHECK(ISC_R_NOMEMORY); + + LOCK_ZONE(zone); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADPENDING); + isc_task_send(zone->loadtask, &e); + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); + + failure: + if (asl != NULL) + isc_mem_put(zone->mctx, asl, sizeof (*asl)); + return (result); +} + +isc_boolean_t +dns__zone_loadpending(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (ISC_TF(DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING))); +} + isc_result_t dns_zone_loadandthaw(dns_zone_t *zone) { isc_result_t result; @@ -1754,7 +1845,7 @@ zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime) { if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS)) options |= DNS_MASTER_MANYERRORS; - if (zone->zmgr != NULL && zone->db != NULL && zone->task != NULL) { + if (zone->zmgr != NULL && zone->db != NULL && zone->loadtask != NULL) { load = isc_mem_get(zone->mctx, sizeof(*load)); if (load == NULL) return (ISC_R_NOMEMORY); @@ -1773,7 +1864,7 @@ zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime) { &load->callbacks.add_private); if (result != ISC_R_SUCCESS) goto cleanup; - result = zonemgr_getio(zone->zmgr, ISC_TRUE, zone->task, + result = zonemgr_getio(zone->zmgr, ISC_TRUE, zone->loadtask, zone_gotreadhandle, load, &zone->readio); if (result != ISC_R_SUCCESS) { @@ -3562,7 +3653,6 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, result = DNS_R_BADZONE; goto cleanup; } - if (zone->type == dns_zone_master && DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRR) && !zone_check_dup(zone, db)) { @@ -3760,6 +3850,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, dns_zone_log(zone, ISC_LOG_INFO, "loaded serial %u%s", serial, dns_db_issecure(db) ? " (DNSSEC signed)" : ""); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING); return (result); cleanup: @@ -3780,6 +3871,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, } else if (zone->type == dns_zone_master || zone->type == dns_zone_redirect) dns_zone_log(zone, ISC_LOG_ERROR, "not loaded due to errors."); + return (result); } @@ -8113,12 +8205,18 @@ zone_maintenance(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); ENTER; + /* + * Are we pending load/reload? + */ + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING)) + return; + /* * Configuring the view of this zone may have * failed, for example because the config file * had a syntax error. In that case, the view - * adb or resolver, and we had better not try - * to do maintenance on it. + * adb or resolver will be NULL, and we had better not try + * to do further maintenance on it. */ if (zone->view == NULL || zone->view->adb == NULL) return; @@ -8239,6 +8337,7 @@ zone_maintenance(dns_zone_t *zone) { set_key_expiry_warning(zone, zone->key_expiry, isc_time_seconds(&now)); break; + default: break; } @@ -13149,6 +13248,7 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, zmgr->timermgr = timermgr; zmgr->socketmgr = socketmgr; zmgr->zonetasks = NULL; + zmgr->loadtasks = NULL; zmgr->task = NULL; zmgr->rl = NULL; ISC_LIST_INIT(zmgr->zones); @@ -13224,6 +13324,7 @@ dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { REQUIRE(zone->zmgr == NULL); isc_taskpool_gettask(zmgr->zonetasks, &zone->task); + isc_taskpool_gettask(zmgr->loadtasks, &zone->loadtask); /* * Set the task name. The tag will arbitrarily point to one @@ -13231,6 +13332,7 @@ dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { * to be managed last). */ isc_task_setname(zone->task, "zone", zone); + isc_task_setname(zone->loadtask, "loadzone", zone); result = isc_timer_create(zmgr->timermgr, isc_timertype_inactive, NULL, NULL, @@ -13238,7 +13340,7 @@ dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { &zone->timer); if (result != ISC_R_SUCCESS) - goto cleanup_task; + goto cleanup_tasks; /* * The timer "holds" a iref. @@ -13252,7 +13354,8 @@ dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { goto unlock; - cleanup_task: + cleanup_tasks: + isc_task_detach(&zone->loadtask); isc_task_detach(&zone->task); unlock: @@ -13368,6 +13471,8 @@ dns_zonemgr_shutdown(dns_zonemgr_t *zmgr) { isc_task_destroy(&zmgr->task); if (zmgr->zonetasks != NULL) isc_taskpool_destroy(&zmgr->zonetasks); + if (zmgr->loadtasks != NULL) + isc_taskpool_destroy(&zmgr->loadtasks); RWLOCK(&zmgr->rwlock, isc_rwlocktype_read); for (zone = ISC_LIST_HEAD(zmgr->zones); @@ -13392,13 +13497,13 @@ dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones) { /* * For anything fewer than 1000 zones we use 10 tasks in - * the task pool. More than that, and we'll scale at one + * the task pools. More than that, and we'll scale at one * task per 100 zones. */ if (ntasks < 10) ntasks = 10; - /* Create or resize the zone task pool. */ + /* Create or resize the zone task pools. */ if (zmgr->zonetasks == NULL) result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, ntasks, 2, &pool); @@ -13408,6 +13513,33 @@ dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones) { if (result == ISC_R_SUCCESS) zmgr->zonetasks = pool; + pool = NULL; + if (zmgr->loadtasks == NULL) + result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, + ntasks, 2, &pool); + else + result = isc_taskpool_expand(&zmgr->loadtasks, ntasks, &pool); + + if (result == ISC_R_SUCCESS) + zmgr->loadtasks = pool; + +#ifdef BIND9 + /* + * We always set all tasks in the zone-load task pool to + * privileged. This prevents other tasks in the system from + * running while the server task manager is in privileged + * mode. + * + * NOTE: If we start using task privileges for any other + * part of the system than zone tasks, then this will need to be + * revisted. In that case we'd want to turn on privileges for + * zone tasks only when we were loading, and turn them off the + * rest of the time. For now, however, it's okay to just + * set it and forget it. + */ + isc_taskpool_setprivilege(zmgr->loadtasks, ISC_TRUE); +#endif + return (result); } @@ -13630,12 +13762,14 @@ zonemgr_getio(dns_zonemgr_t *zmgr, isc_boolean_t high, io = isc_mem_get(zmgr->mctx, sizeof(*io)); if (io == NULL) return (ISC_R_NOMEMORY); + io->event = isc_event_allocate(zmgr->mctx, task, DNS_EVENT_IOREADY, action, arg, sizeof(*io->event)); if (io->event == NULL) { isc_mem_put(zmgr->mctx, io, sizeof(*io)); return (ISC_R_NOMEMORY); } + io->zmgr = zmgr; io->high = high; io->task = NULL; @@ -13655,9 +13789,8 @@ zonemgr_getio(dns_zonemgr_t *zmgr, isc_boolean_t high, UNLOCK(&zmgr->iolock); *iop = io; - if (!queue) { + if (!queue) isc_task_send(io->task, &io->event); - } return (ISC_R_SUCCESS); } diff --git a/lib/dns/zt.c b/lib/dns/zt.c index 8f9231b75b..5e0d9fd708 100644 --- a/lib/dns/zt.c +++ b/lib/dns/zt.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: zt.c,v 1.50 2011/03/21 23:47:21 tbox Exp $ */ +/* $Id: zt.c,v 1.51 2011/09/02 21:15:36 each Exp $ */ /*! \file */ @@ -42,8 +42,11 @@ struct dns_zt { isc_mem_t *mctx; dns_rdataclass_t rdclass; isc_rwlock_t rwlock; + dns_zt_allloaded_t loaddone; + void * loaddone_arg; /* Locked by lock. */ isc_uint32_t references; + unsigned int loads_pending; dns_rbt_t *table; }; @@ -56,12 +59,21 @@ auto_detach(void *, void *); static isc_result_t load(dns_zone_t *zone, void *uap); +static isc_result_t +asyncload(dns_zone_t *zone, void *callback); + static isc_result_t loadnew(dns_zone_t *zone, void *uap); static isc_result_t freezezones(dns_zone_t *zone, void *uap); +void +startloading(dns_zt_t *zt, dns_zone_t *zone); + +void +doneloading(dns_zt_t *zt, dns_zone_t *zone); + isc_result_t dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **ztp) { @@ -87,6 +99,9 @@ dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **ztp) zt->references = 1; zt->rdclass = rdclass; zt->magic = ZTMAGIC; + zt->loaddone = NULL; + zt->loaddone_arg = NULL; + zt->loads_pending = 0; *ztp = zt; return (ISC_R_SUCCESS); @@ -243,12 +258,60 @@ static isc_result_t load(dns_zone_t *zone, void *uap) { isc_result_t result; UNUSED(uap); + result = dns_zone_load(zone); if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE) result = ISC_R_SUCCESS; + return (result); } +isc_result_t +dns_zt_asyncload(dns_zt_t *zt, dns_zt_allloaded_t alldone, void *arg) { + isc_result_t result, tresult; + int pending; + + REQUIRE(VALID_ZT(zt)); + + RWLOCK(&zt->rwlock, isc_rwlocktype_read); + + INSIST(zt->loads_pending == 0); + + result = dns_zt_apply2(zt, ISC_FALSE, &tresult, asyncload, doneloading); + + pending = zt->loads_pending; + if (pending != 0) { + zt->loaddone = alldone; + zt->loaddone_arg = arg; + } + + RWUNLOCK(&zt->rwlock, isc_rwlocktype_read); + + if (pending == 0) + alldone(arg); + + return (result); +} + +/* + * Initiates asynchronous loading of zone 'zone'. 'callback' is a + * pointer to a function which will be used to inform the caller when + * the zone loading is complete. + */ +static isc_result_t +asyncload(dns_zone_t *zone, void *callback) { + isc_result_t result; + dns_zt_t *zt; + + REQUIRE(zone != NULL); + zt = dns_zone_getview(zone)->zonetable; + + result = dns_zone_asyncload(zone, (dns_zt_zoneloaded_t) callback, zt); + if (result == ISC_R_SUCCESS) + zt->loads_pending++; + return (ISC_R_SUCCESS); +} + isc_result_t dns_zt_loadnew(dns_zt_t *zt, isc_boolean_t stop) { isc_result_t result; @@ -265,6 +328,7 @@ static isc_result_t loadnew(dns_zone_t *zone, void *uap) { isc_result_t result; UNUSED(uap); + result = dns_zone_loadnew(zone); if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE || result == DNS_R_DYNAMIC) @@ -281,6 +345,8 @@ dns_zt_freezezones(dns_zt_t *zt, isc_boolean_t freeze) { RWLOCK(&zt->rwlock, isc_rwlocktype_read); result = dns_zt_apply2(zt, ISC_FALSE, &tresult, freezezones, &freeze); RWUNLOCK(&zt->rwlock, isc_rwlocktype_read); + if (tresult == ISC_R_NOTFOUND) + tresult = ISC_R_SUCCESS; return ((result == ISC_R_SUCCESS) ? tresult : result); } @@ -364,6 +430,7 @@ dns_zt_apply2(dns_zt_t *zt, isc_boolean_t stop, isc_result_t *sub, /* * The tree is empty. */ + tresult = result; result = ISC_R_NOMORE; } while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { @@ -393,6 +460,35 @@ dns_zt_apply2(dns_zt_t *zt, isc_boolean_t stop, isc_result_t *sub, return (result); } +/* + * Decrement the loads_pending counter; when counter reaches + * zero, call the loaddone callback that was initially set by + * dns_zt_asyncload(). + */ +void +doneloading(dns_zt_t *zt, dns_zone_t *zone) { + dns_zt_allloaded_t alldone = NULL; + void *arg = NULL; + + UNUSED(zone); + + REQUIRE(VALID_ZT(zt)); + + RWLOCK(&zt->rwlock, isc_rwlocktype_write); + INSIST(zt->loads_pending != 0); + zt->loads_pending--; + if (zt->loads_pending == 0) { + alldone = zt->loaddone; + arg = zt->loaddone_arg; + zt->loaddone = NULL; + zt->loaddone_arg = NULL; + } + RWUNLOCK(&zt->rwlock, isc_rwlocktype_write); + + if (alldone != NULL) + alldone(arg); +} + /*** *** Private ***/ diff --git a/lib/isc/include/isc/list.h b/lib/isc/include/isc/list.h index 9338275c94..2e62431592 100644 --- a/lib/isc/include/isc/list.h +++ b/lib/isc/include/isc/list.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: list.h,v 1.24 2007/06/19 23:47:18 tbox Exp $ */ +/* $Id: list.h,v 1.25 2011/09/02 21:15:38 each Exp $ */ #ifndef ISC_LIST_H #define ISC_LIST_H 1 @@ -169,6 +169,19 @@ (list2).tail = NULL; \ } while (0) +#define ISC_LIST_PREPENDLIST(list1, list2, link) \ + do { \ + if (ISC_LIST_EMPTY(list1)) \ + (list1) = (list2); \ + else if (!ISC_LIST_EMPTY(list2)) { \ + (list2).tail->link.next = (list1).head; \ + (list1).head->link.prev = (list2).tail; \ + (list1).head = (list2).head; \ + } \ + (list2).head = NULL; \ + (list2).tail = NULL; \ + } while (0) + #define ISC_LIST_ENQUEUE(list, elt, link) ISC_LIST_APPEND(list, elt, link) #define __ISC_LIST_ENQUEUEUNSAFE(list, elt, link) \ __ISC_LIST_APPENDUNSAFE(list, elt, link) diff --git a/lib/isc/include/isc/namespace.h b/lib/isc/include/isc/namespace.h index c111d2873b..ee5723e7ae 100644 --- a/lib/isc/include/isc/namespace.h +++ b/lib/isc/include/isc/namespace.h @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: namespace.h,v 1.12 2011/08/23 17:02:53 each Exp $ */ +/* $Id: namespace.h,v 1.13 2011/09/02 21:15:38 each Exp $ */ #ifndef ISCAPI_NAMESPACE_H #define ISCAPI_NAMESPACE_H 1 @@ -31,6 +31,7 @@ #define isc_app_run isc__app_run #define isc_app_ctxrun isc__app_ctxrun #define isc_app_shutdown isc__app_shutdown +#define isc_app_ctxfinish isc__app_ctxfinish #define isc_app_ctxshutdown isc__app_ctxshutdown #define isc_app_ctxsuspend isc__app_ctxsuspend #define isc_app_reload isc__app_reload @@ -147,9 +148,13 @@ #define isc_task_gettag isc__task_gettag #define isc_task_getcurrenttime isc__task_getcurrenttime #define isc_taskmgr_create isc__taskmgr_create +#define isc_taskmgr_setmode isc__taskmgr_setmode +#define isc_taskmgr_mode isc__taskmgr_mode #define isc_taskmgr_destroy isc__taskmgr_destroy #define isc_task_beginexclusive isc__task_beginexclusive #define isc_task_endexclusive isc__task_endexclusive +#define isc_task_setprivilege isc__task_setprivilege +#define isc_task_privilege isc__task_privilege #define isc_timer_create isc__timer_create #define isc_timer_reset isc__timer_reset diff --git a/lib/isc/include/isc/task.h b/lib/isc/include/isc/task.h index de84ea181f..fc256db813 100644 --- a/lib/isc/include/isc/task.h +++ b/lib/isc/include/isc/task.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: task.h,v 1.71 2011/02/03 12:18:12 tbox Exp $ */ +/* $Id: task.h,v 1.72 2011/09/02 21:15:38 each Exp $ */ #ifndef ISC_TASK_H #define ISC_TASK_H 1 @@ -88,6 +88,7 @@ #define ISC_TASKEVENT_FIRSTEVENT (ISC_EVENTCLASS_TASK + 0) #define ISC_TASKEVENT_SHUTDOWN (ISC_EVENTCLASS_TASK + 1) +#define ISC_TASKEVENT_TEST (ISC_EVENTCLASS_TASK + 1) #define ISC_TASKEVENT_LASTEVENT (ISC_EVENTCLASS_TASK + 65535) /***** @@ -100,9 +101,17 @@ ISC_LANG_BEGINDECLS *** Types ***/ +typedef enum { + isc_taskmgrmode_normal = 0, + isc_taskmgrmode_privileged +} isc_taskmgrmode_t; + /*% Task and task manager methods */ typedef struct isc_taskmgrmethods { void (*destroy)(isc_taskmgr_t **managerp); + void (*setmode)(isc_taskmgr_t *manager, + isc_taskmgrmode_t mode); + isc_taskmgrmode_t (*mode)(isc_taskmgr_t *manager); isc_result_t (*taskcreate)(isc_taskmgr_t *manager, unsigned int quantum, isc_task_t **taskp); @@ -127,6 +136,8 @@ typedef struct isc_taskmethods { void *tag); isc_result_t (*beginexclusive)(isc_task_t *task); void (*endexclusive)(isc_task_t *task); + void (*setprivilege)(isc_task_t *task, isc_boolean_t priv); + isc_boolean_t (*privilege)(isc_task_t *task); } isc_taskmethods_t; /*% @@ -611,6 +622,32 @@ isc_task_exiting(isc_task_t *t); *\li 'task' is a valid task. */ +void +isc_task_setprivilege(isc_task_t *task, isc_boolean_t priv); +/*%< + * Set or unset the task's "privileged" flag depending on the value of + * 'priv'. + * + * Under normal circumstances this flag has no effect on the task behavior, + * but when the task manager has been set to privileged exeuction mode via + * isc_taskmgr_setmode(), only tasks with the flag set will be executed, + * and all other tasks will wait until they're done. Once all privileged + * tasks have finished executing, the task manager will automatically + * return to normal execution mode and nonprivileged task can resume. + * + * Requires: + *\li 'task' is a valid task. + */ + +isc_boolean_t +isc_task_privilege(isc_task_t *task); +/*%< + * Returns the current value of the task's privilege flag. + * + * Requires: + *\li 'task' is a valid task. + */ + /***** ***** Task Manager. *****/ @@ -663,6 +700,31 @@ isc_taskmgr_create(isc_mem_t *mctx, unsigned int workers, * manager shutting down. */ +void +isc_taskmgr_setmode(isc_taskmgr_t *manager, isc_taskmgrmode_t mode); + +isc_taskmgrmode_t +isc_taskmgr_mode(isc_taskmgr_t *manager); +/*%< + * Set/get the current operating mode of the task manager. Valid modes are: + * + *\li isc_taskmgrmode_normal + *\li isc_taskmgrmode_privileged + * + * In privileged execution mode, only tasks that have had the "privilege" + * flag set via isc_task_setprivilege() can be executed. When all such + * tasks are complete, the manager automatically returns to normal mode + * and proceeds with running non-privileged ready tasks. This means it is + * necessary to have at least one privileged task waiting on the ready + * queue *before* setting the manager into privileged execution mode, + * which in turn means the task which calls this function should be in + * task-exclusive mode when it does so. + * + * Requires: + * + *\li 'manager' is a valid task manager. + */ + void isc_taskmgr_destroy(isc_taskmgr_t **managerp); /*%< diff --git a/lib/isc/include/isc/taskpool.h b/lib/isc/include/isc/taskpool.h index 14a1f60ea3..4d106ada2f 100644 --- a/lib/isc/include/isc/taskpool.h +++ b/lib/isc/include/isc/taskpool.h @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: taskpool.h,v 1.17 2011/07/07 23:47:50 tbox Exp $ */ +/* $Id: taskpool.h,v 1.18 2011/09/02 21:15:38 each Exp $ */ #ifndef ISC_TASKPOOL_H #define ISC_TASKPOOL_H 1 @@ -139,6 +139,19 @@ isc_taskpool_destroy(isc_taskpool_t **poolp); * \li '*poolp' is a valid task pool. */ +void +isc_taskpool_setprivilege(isc_taskpool_t *pool, isc_boolean_t priv); +/*%< + * Set the privilege flag on all tasks in 'pool' to 'priv'. If 'priv' is + * true, then when the task manager is set into privileged mode, only + * tasks wihin this pool will be able to execute. (Note: It is important + * to turn the pool tasks' privilege back off before the last task finishes + * executing.) + * + * Requires: + * \li 'pool' is a valid task pool. + */ + ISC_LANG_ENDDECLS #endif /* ISC_TASKPOOL_H */ diff --git a/lib/isc/task.c b/lib/isc/task.c index 7cf5d596ad..75df593804 100644 --- a/lib/isc/task.c +++ b/lib/isc/task.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: task.c,v 1.117 2011/02/03 12:18:12 tbox Exp $ */ +/* $Id: task.c,v 1.118 2011/09/02 21:15:37 each Exp $ */ /*! \file * \author Principal Author: Bob Halley @@ -64,9 +64,7 @@ #endif /* ISC_PLATFORM_USETHREADS */ #endif /* BIND9 */ -#ifndef USE_WORKER_THREADS #include "task_p.h" -#endif /* USE_WORKER_THREADS */ #ifdef ISC_TASK_TRACE #define XTRACE(m) fprintf(stderr, "task %p thread %lu: %s\n", \ @@ -120,9 +118,11 @@ struct isc__task { /* Locked by task manager lock. */ LINK(isc__task_t) link; LINK(isc__task_t) ready_link; + LINK(isc__task_t) ready_priority_link; }; #define TASK_F_SHUTTINGDOWN 0x01 +#define TASK_F_PRIVILEGED 0x02 #define TASK_SHUTTINGDOWN(t) (((t)->flags & TASK_F_SHUTTINGDOWN) \ != 0) @@ -145,11 +145,15 @@ struct isc__taskmgr { unsigned int default_quantum; LIST(isc__task_t) tasks; isc__tasklist_t ready_tasks; + isc__tasklist_t ready_priority_tasks; + isc_taskmgrmode_t mode; #ifdef ISC_PLATFORM_USETHREADS isc_condition_t work_available; isc_condition_t exclusive_granted; + isc_condition_t paused; #endif /* ISC_PLATFORM_USETHREADS */ unsigned int tasks_running; + isc_boolean_t pause_requested; isc_boolean_t exclusive_requested; isc_boolean_t exiting; #ifdef USE_SHARED_MANAGER @@ -225,6 +229,23 @@ ISC_TASKFUNC_SCOPE isc_result_t isc__task_beginexclusive(isc_task_t *task); ISC_TASKFUNC_SCOPE void isc__task_endexclusive(isc_task_t *task0); +ISC_TASKFUNC_SCOPE void +isc__task_setprivilege(isc_task_t *task0, isc_boolean_t priv); +ISC_TASKFUNC_SCOPE isc_boolean_t +isc__task_privilege(isc_task_t *task0); +ISC_TASKFUNC_SCOPE void +isc__taskmgr_setmode(isc_taskmgr_t *manager0, isc_taskmgrmode_t mode); +ISC_TASKFUNC_SCOPE isc_taskmgrmode_t +isc__taskmgr_mode(isc_taskmgr_t *manager0); + +static isc_boolean_t +empty_readyq(isc__taskmgr_t *manager); + +static isc__task_t * +pop_readyq(isc__taskmgr_t *manager); + +static void +push_readyq(isc__taskmgr_t *manager, isc__task_t *task); static struct isc__taskmethods { isc_taskmethods_t methods; @@ -249,7 +270,9 @@ static struct isc__taskmethods { isc__task_purge, isc__task_purgerange, isc__task_beginexclusive, - isc__task_endexclusive + isc__task_endexclusive, + isc__task_setprivilege, + isc__task_privilege } #ifndef BIND9 , @@ -261,6 +284,8 @@ static struct isc__taskmethods { static isc_taskmgrmethods_t taskmgrmethods = { isc__taskmgr_destroy, + isc__taskmgr_setmode, + isc__taskmgr_mode, isc__task_create }; @@ -333,6 +358,7 @@ isc__task_create(isc_taskmgr_t *manager0, unsigned int quantum, task->tag = NULL; INIT_LINK(task, link); INIT_LINK(task, ready_link); + INIT_LINK(task, ready_priority_link); exiting = ISC_FALSE; LOCK(&manager->lock); @@ -400,6 +426,7 @@ task_shutdown(isc__task_t *task) { } INSIST(task->state == task_state_ready || task->state == task_state_running); + /* * Note that we post shutdown events LIFO. */ @@ -415,9 +442,17 @@ task_shutdown(isc__task_t *task) { return (was_idle); } +/* + * Moves a task onto the appropriate run queue. + * + * Caller must NOT hold manager lock. + */ static inline void task_ready(isc__task_t *task) { isc__taskmgr_t *manager = task->manager; +#ifdef USE_WORKER_THREADS + isc_boolean_t has_privilege = isc__task_privilege((isc_task_t *) task); +#endif /* USE_WORKER_THREADS */ REQUIRE(VALID_MANAGER(manager)); REQUIRE(task->state == task_state_ready); @@ -425,12 +460,11 @@ task_ready(isc__task_t *task) { XTRACE("task_ready"); LOCK(&manager->lock); - - ENQUEUE(manager->ready_tasks, task, ready_link); + push_readyq(manager, task); #ifdef USE_WORKER_THREADS - SIGNAL(&manager->work_available); + if (manager->mode == isc_taskmgrmode_normal || has_privilege) + SIGNAL(&manager->work_available); #endif /* USE_WORKER_THREADS */ - UNLOCK(&manager->lock); } @@ -868,21 +902,81 @@ isc__task_getcurrenttime(isc_task_t *task0, isc_stdtime_t *t) { REQUIRE(t != NULL); LOCK(&task->lock); - *t = task->now; - UNLOCK(&task->lock); } /*** *** Task Manager. ***/ + +/* + * Return ISC_TRUE if the current ready list for the manager, which is + * either ready_tasks or the ready_priority_tasks, depending on whether + * the manager is currently in normal or privileged execution mode. + * + * Caller must hold the task manager lock. + */ +static inline isc_boolean_t +empty_readyq(isc__taskmgr_t *manager) { + isc__tasklist_t queue; + + if (manager->mode == isc_taskmgrmode_normal) + queue = manager->ready_tasks; + else + queue = manager->ready_priority_tasks; + + return (ISC_TF(EMPTY(queue))); +} + +/* + * Dequeue and return a pointer to the first task on the current ready + * list for the manager. + * If the task is privileged, dequeue it from the other ready list + * as well. + * + * Caller must hold the task manager lock. + */ +static inline isc__task_t * +pop_readyq(isc__taskmgr_t *manager) { + isc__task_t *task; + + if (manager->mode == isc_taskmgrmode_normal) + task = HEAD(manager->ready_tasks); + else + task = HEAD(manager->ready_priority_tasks); + + if (task != NULL) { + DEQUEUE(manager->ready_tasks, task, ready_link); + if (ISC_LINK_LINKED(task, ready_priority_link)) + DEQUEUE(manager->ready_priority_tasks, task, + ready_priority_link); + } + + return (task); +} + +/* + * Push 'task' onto the ready_tasks queue. If 'task' has the privilege + * flag set, then also push it onto the ready_priority_tasks queue. + * + * Caller must hold the task manager lock. + */ +static inline void +push_readyq(isc__taskmgr_t *manager, isc__task_t *task) { + ENQUEUE(manager->ready_tasks, task, ready_link); + if ((task->flags & TASK_F_PRIVILEGED) != 0) + ENQUEUE(manager->ready_priority_tasks, task, + ready_priority_link); +} + static void dispatch(isc__taskmgr_t *manager) { isc__task_t *task; #ifndef USE_WORKER_THREADS unsigned int total_dispatch_count = 0; - isc__tasklist_t ready_tasks; + isc__tasklist_t new_ready_tasks; + isc__tasklist_t new_priority_tasks; #endif /* USE_WORKER_THREADS */ REQUIRE(VALID_MANAGER(manager)); @@ -938,9 +1032,11 @@ dispatch(isc__taskmgr_t *manager) { */ #ifndef USE_WORKER_THREADS - ISC_LIST_INIT(ready_tasks); + ISC_LIST_INIT(new_ready_tasks); + ISC_LIST_INIT(new_priority_tasks); #endif LOCK(&manager->lock); + while (!FINISHED(manager)) { #ifdef USE_WORKER_THREADS /* @@ -949,10 +1045,12 @@ dispatch(isc__taskmgr_t *manager) { * the task while only holding the manager lock, and then * change the task to running state while only holding the * task lock. + * + * If a pause has been requested, don't do any work + * until it's been released. */ - while ((EMPTY(manager->ready_tasks) || - manager->exclusive_requested) && - !FINISHED(manager)) + while ((empty_readyq(manager) || manager->pause_requested || + manager->exclusive_requested) && !FINISHED(manager)) { XTHREADTRACE(isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL, @@ -964,13 +1062,13 @@ dispatch(isc__taskmgr_t *manager) { } #else /* USE_WORKER_THREADS */ if (total_dispatch_count >= DEFAULT_TASKMGR_QUANTUM || - EMPTY(manager->ready_tasks)) + empty_readyq(manager)) break; #endif /* USE_WORKER_THREADS */ XTHREADTRACE(isc_msgcat_get(isc_msgcat, ISC_MSGSET_TASK, ISC_MSG_WORKING, "working")); - task = HEAD(manager->ready_tasks); + task = pop_readyq(manager); if (task != NULL) { unsigned int dispatch_count = 0; isc_boolean_t done = ISC_FALSE; @@ -985,7 +1083,6 @@ dispatch(isc__taskmgr_t *manager) { * have a task to do. We must reacquire the manager * lock before exiting the 'if (task != NULL)' block. */ - DEQUEUE(manager->ready_tasks, task, ready_link); manager->tasks_running++; UNLOCK(&manager->lock); @@ -1106,6 +1203,9 @@ dispatch(isc__taskmgr_t *manager) { if (manager->exclusive_requested && manager->tasks_running == 1) { SIGNAL(&manager->exclusive_granted); + } else if (manager->pause_requested && + manager->tasks_running == 0) { + SIGNAL(&manager->paused); } #endif /* USE_WORKER_THREADS */ if (requeue) { @@ -1129,17 +1229,39 @@ dispatch(isc__taskmgr_t *manager) { * might even hurt rather than help. */ #ifdef USE_WORKER_THREADS - ENQUEUE(manager->ready_tasks, task, - ready_link); + push_readyq(manager, task); #else - ENQUEUE(ready_tasks, task, ready_link); + ENQUEUE(new_ready_tasks, task, ready_link); + if ((task->flags & TASK_F_PRIVILEGED) != 0) + ENQUEUE(new_priority_tasks, task, + ready_priority_link); #endif } } - } -#ifndef USE_WORKER_THREADS - ISC_LIST_APPENDLIST(manager->ready_tasks, ready_tasks, ready_link); + +#ifdef USE_WORKER_THREADS + /* + * If we are in privileged execution mode and there are no + * tasks remaining on the current ready queue, then + * we're stuck. Automatically drop privileges at that + * point and continue with the regular ready queue. + */ + if (manager->tasks_running == 0 && empty_readyq(manager)) { + manager->mode = isc_taskmgrmode_normal; + if (!empty_readyq(manager)) + BROADCAST(&manager->work_available); + } #endif + } + +#ifndef USE_WORKER_THREADS + ISC_LIST_APPENDLIST(&manager->ready_tasks, new_ready_tasks, ready_link); + ISC_LIST_APPENDLIST(&manager->ready_priority_tasks, new_priority_tasks, + ready_priority_link); + if (empty_readyq(manager)) + manager->mode == isc_taskmgrmode_normal; +#endif + UNLOCK(&manager->lock); } @@ -1174,6 +1296,7 @@ manager_free(isc__taskmgr_t *manager) { #ifdef USE_WORKER_THREADS (void)isc_condition_destroy(&manager->exclusive_granted); (void)isc_condition_destroy(&manager->work_available); + (void)isc_condition_destroy(&manager->paused); isc_mem_free(manager->mctx, manager->threads); #endif /* USE_WORKER_THREADS */ DESTROYLOCK(&manager->lock); @@ -1224,6 +1347,7 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers, manager->common.methods = &taskmgrmethods; manager->common.impmagic = TASK_MANAGER_MAGIC; manager->common.magic = ISCAPI_TASKMGR_MAGIC; + manager->mode = isc_taskmgrmode_normal; manager->mctx = NULL; result = isc_mutex_init(&manager->lock); if (result != ISC_R_SUCCESS) @@ -1253,14 +1377,24 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers, result = ISC_R_UNEXPECTED; goto cleanup_workavailable; } + if (isc_condition_init(&manager->paused) != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_condition_init() %s", + isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL, + ISC_MSG_FAILED, "failed")); + result = ISC_R_UNEXPECTED; + goto cleanup_exclusivegranted; + } #endif /* USE_WORKER_THREADS */ if (default_quantum == 0) default_quantum = DEFAULT_DEFAULT_QUANTUM; manager->default_quantum = default_quantum; INIT_LIST(manager->tasks); INIT_LIST(manager->ready_tasks); + INIT_LIST(manager->ready_priority_tasks); manager->tasks_running = 0; manager->exclusive_requested = ISC_FALSE; + manager->pause_requested = ISC_FALSE; manager->exiting = ISC_FALSE; isc_mem_attach(mctx, &manager->mctx); @@ -1296,6 +1430,8 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers, return (ISC_R_SUCCESS); #ifdef USE_WORKER_THREADS + cleanup_exclusivegranted: + (void)isc_condition_destroy(&manager->exclusive_granted); cleanup_workavailable: (void)isc_condition_destroy(&manager->work_available); cleanup_threads: @@ -1360,6 +1496,11 @@ isc__taskmgr_destroy(isc_taskmgr_t **managerp) { INSIST(!manager->exiting); manager->exiting = ISC_TRUE; + /* + * If privileged mode was on, turn it off. + */ + manager->mode = isc_taskmgrmode_normal; + /* * Post shutdown event(s) to every task (if they haven't already been * posted). @@ -1369,7 +1510,7 @@ isc__taskmgr_destroy(isc_taskmgr_t **managerp) { task = NEXT(task, link)) { LOCK(&task->lock); if (task_shutdown(task)) - ENQUEUE(manager->ready_tasks, task, ready_link); + push_readyq(manager, task); UNLOCK(&task->lock); } #ifdef USE_WORKER_THREADS @@ -1408,10 +1549,30 @@ isc__taskmgr_destroy(isc_taskmgr_t **managerp) { *managerp = NULL; } +ISC_TASKFUNC_SCOPE void +isc__taskmgr_setmode(isc_taskmgr_t *manager0, isc_taskmgrmode_t mode) { + isc__taskmgr_t *manager = (isc__taskmgr_t *)manager0; + + LOCK(&manager->lock); + manager->mode = mode; + UNLOCK(&manager->lock); +} + +ISC_TASKFUNC_SCOPE isc_taskmgrmode_t +isc__taskmgr_mode(isc_taskmgr_t *manager0) { + isc__taskmgr_t *manager = (isc__taskmgr_t *)manager0; + isc_taskmgrmode_t mode; + LOCK(&manager->lock); + mode = manager->mode; + UNLOCK(&manager->lock); + return (mode); +} + #ifndef USE_WORKER_THREADS isc_boolean_t isc__taskmgr_ready(isc_taskmgr_t *manager0) { isc__taskmgr_t *manager = (isc__taskmgr_t *)manager0; + isc_boolean_t is_ready; #ifdef USE_SHARED_MANAGER if (manager == NULL) @@ -1419,7 +1580,12 @@ isc__taskmgr_ready(isc_taskmgr_t *manager0) { #endif if (manager == NULL) return (ISC_FALSE); - return (ISC_TF(!ISC_LIST_EMPTY(manager->ready_tasks))); + + LOCK(&manager->lock); + is_ready = !empty_readyq(manager); + UNLOCK(&manager->lock); + + return (is_ready); } isc_result_t @@ -1438,6 +1604,29 @@ isc__taskmgr_dispatch(isc_taskmgr_t *manager0) { return (ISC_R_SUCCESS); } +#else +ISC_TASKFUNC_SCOPE void +isc__taskmgr_pause(isc_taskmgr_t *manager0) { + isc__taskmgr_t *manager = (isc__taskmgr_t *)manager0; + LOCK(&manager->lock); + while (manager->tasks_running > 0) { + WAIT(&manager->paused, &manager->lock); + } + manager->pause_requested = ISC_TRUE; + UNLOCK(&manager->lock); +} + +ISC_TASKFUNC_SCOPE void +isc__taskmgr_resume(isc_taskmgr_t *manager0) { + isc__taskmgr_t *manager = (isc__taskmgr_t *)manager0; + + LOCK(&manager->lock); + if (manager->pause_requested) { + manager->pause_requested = ISC_FALSE; + BROADCAST(&manager->work_available); + } + UNLOCK(&manager->lock); +} #endif /* USE_WORKER_THREADS */ ISC_TASKFUNC_SCOPE isc_result_t @@ -1479,6 +1668,44 @@ isc__task_endexclusive(isc_task_t *task0) { #endif } +ISC_TASKFUNC_SCOPE void +isc__task_setprivilege(isc_task_t *task0, isc_boolean_t priv) { + isc__task_t *task = (isc__task_t *)task0; + isc__taskmgr_t *manager = task->manager; + isc_boolean_t oldpriv; + + LOCK(&task->lock); + oldpriv = ISC_TF((task->flags & TASK_F_PRIVILEGED) != 0); + if (priv) + task->flags |= TASK_F_PRIVILEGED; + else + task->flags &= ~TASK_F_PRIVILEGED; + UNLOCK(&task->lock); + + if (priv == oldpriv) + return; + + LOCK(&manager->lock); + if (priv && ISC_LINK_LINKED(task, ready_link)) + ENQUEUE(manager->ready_priority_tasks, task, + ready_priority_link); + else if (!priv && ISC_LINK_LINKED(task, ready_priority_link)) + DEQUEUE(manager->ready_priority_tasks, task, + ready_priority_link); + UNLOCK(&manager->lock); +} + +ISC_TASKFUNC_SCOPE isc_boolean_t +isc__task_privilege(isc_task_t *task0) { + isc__task_t *task = (isc__task_t *)task0; + isc_boolean_t priv; + + LOCK(&task->lock); + priv = ISC_TF((task->flags & TASK_F_PRIVILEGED) != 0); + UNLOCK(&task->lock); + return (priv); +} + #ifdef USE_SOCKETIMPREGISTER isc_result_t isc__task_register() { diff --git a/lib/isc/task_api.c b/lib/isc/task_api.c index ac36d75c46..a1b53c2db3 100644 --- a/lib/isc/task_api.c +++ b/lib/isc/task_api.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: task_api.c,v 1.7 2010/12/22 23:46:59 tbox Exp $ */ +/* $Id: task_api.c,v 1.8 2011/09/02 21:15:38 each Exp $ */ #include @@ -99,6 +99,20 @@ isc_taskmgr_destroy(isc_taskmgr_t **managerp) { ENSURE(*managerp == NULL); } +void +isc_taskmgr_setmode(isc_taskmgr_t *manager, isc_taskmgrmode_t mode) { + REQUIRE(ISCAPI_TASKMGR_VALID(manager)); + + manager->methods->setmode(manager, mode); +} + +isc_taskmgrmode_t +isc_taskmgr_mode(isc_taskmgr_t *manager) { + REQUIRE(ISCAPI_TASKMGR_VALID(manager)); + + return (manager->methods->mode(manager)); +} + isc_result_t isc_task_create(isc_taskmgr_t *manager, unsigned int quantum, isc_task_t **taskp) @@ -201,6 +215,20 @@ isc_task_endexclusive(isc_task_t *task) { task->methods->endexclusive(task); } +void +isc_task_setprivilege(isc_task_t *task, isc_boolean_t priv) { + REQUIRE(ISCAPI_TASK_VALID(task)); + + task->methods->setprivilege(task, priv); +} + +isc_boolean_t +isc_task_privilege(isc_task_t *task) { + REQUIRE(ISCAPI_TASK_VALID(task)); + + return (task->methods->privilege(task)); +} + /*% * This is necessary for libisc's internal timer implementation. Other diff --git a/lib/isc/task_p.h b/lib/isc/task_p.h index cab2a83978..b7d463fceb 100644 --- a/lib/isc/task_p.h +++ b/lib/isc/task_p.h @@ -15,17 +15,25 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: task_p.h,v 1.13 2009/09/02 23:48:02 tbox Exp $ */ +/* $Id: task_p.h,v 1.14 2011/09/02 21:15:38 each Exp $ */ #ifndef ISC_TASK_P_H #define ISC_TASK_P_H /*! \file */ +#if defined(BIND9) && defined(ISC_PLATFORM_USETHREADS) +void +isc__taskmgr_pause(isc_taskmgr_t *taskmgr); + +void +isc__taskmgr_resume(isc_taskmgr_t *taskmgr); +#else isc_boolean_t isc__taskmgr_ready(isc_taskmgr_t *taskmgr); isc_result_t isc__taskmgr_dispatch(isc_taskmgr_t *taskmgr); +#endif /* !BIND9 || !ISC_PLATFORM_USETHREADS */ #endif /* ISC_TASK_P_H */ diff --git a/lib/isc/taskpool.c b/lib/isc/taskpool.c index 49f9832dd1..a22bd1c3a4 100644 --- a/lib/isc/taskpool.c +++ b/lib/isc/taskpool.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: taskpool.c,v 1.20 2011/07/07 23:47:49 tbox Exp $ */ +/* $Id: taskpool.c,v 1.21 2011/09/02 21:15:38 each Exp $ */ /*! \file */ @@ -163,9 +163,8 @@ isc_taskpool_destroy(isc_taskpool_t **poolp) { unsigned int i; isc_taskpool_t *pool = *poolp; for (i = 0; i < pool->ntasks; i++) { - if (pool->tasks[i] != NULL) { + if (pool->tasks[i] != NULL) isc_task_detach(&pool->tasks[i]); - } } isc_mem_put(pool->mctx, pool->tasks, pool->ntasks * sizeof(isc_task_t *)); @@ -173,4 +172,14 @@ isc_taskpool_destroy(isc_taskpool_t **poolp) { *poolp = NULL; } +void +isc_taskpool_setprivilege(isc_taskpool_t *pool, isc_boolean_t priv) { + unsigned int i; + REQUIRE(pool != NULL); + + for (i = 0; i < pool->ntasks; i++) { + if (pool->tasks[i] != NULL) + isc_task_setprivilege(pool->tasks[i], priv); + } +} diff --git a/lib/isc/tests/Makefile.in b/lib/isc/tests/Makefile.in index 88fb55a02e..9c4fdb6fc3 100644 --- a/lib/isc/tests/Makefile.in +++ b/lib/isc/tests/Makefile.in @@ -12,7 +12,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: Makefile.in,v 1.6 2011/08/23 01:29:38 each Exp $ +# $Id: Makefile.in,v 1.7 2011/09/02 21:15:38 each Exp $ srcdir = @srcdir@ VPATH = @srcdir@ @@ -35,10 +35,12 @@ ISCDEPLIBS = ../libisc.@A@ LIBS = @LIBS@ @ATFLIBS@ OBJS = isctest.@O@ -SRCS = isctest.c taskpool_test.c socket_test.c hash_test.c +SRCS = isctest.c taskpool_test.c socket_test.c hash_test.c \ + task_test.c SUBDIRS = -TARGETS = taskpool_test@EXEEXT@ socket_test@EXEEXT@ hash_test@EXEEXT@ +TARGETS = taskpool_test@EXEEXT@ socket_test@EXEEXT@ hash_test@EXEEXT@ \ + task_test@EXEEXT@ @BIND9_MAKE_RULES@ @@ -46,6 +48,10 @@ taskpool_test@EXEEXT@: taskpool_test.@O@ isctest.@O@ ${ISCDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ taskpool_test.@O@ isctest.@O@ ${ISCLIBS} ${LIBS} +task_test@EXEEXT@: task_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + task_test.@O@ isctest.@O@ ${ISCLIBS} ${LIBS} + socket_test@EXEEXT@: socket_test.@O@ isctest.@O@ ${ISCDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ socket_test.@O@ isctest.@O@ ${ISCLIBS} ${LIBS} diff --git a/lib/isc/tests/isctest.c b/lib/isc/tests/isctest.c index 6d0eecfe7f..0aea1bdc82 100644 --- a/lib/isc/tests/isctest.c +++ b/lib/isc/tests/isctest.c @@ -14,12 +14,14 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: isctest.c,v 1.3 2011/07/28 04:04:37 each Exp $ */ +/* $Id: isctest.c,v 1.4 2011/09/02 21:15:38 each Exp $ */ /*! \file */ #include +#include + #include #include #include @@ -153,3 +155,24 @@ isc_test_end() { isc_mem_destroy(&mctx); } +/* + * Sleep for 'usec' microseconds. + */ +void +isc_test_nap(isc_uint32_t usec) { +#ifdef HAVE_NANOSLEEP + struct timespec ts; + + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + nanosleep(&ts, NULL); +#elif HAVE_USLEEP + usleep(usec); +#else + /* + * No fractional-second sleep function is available, so we + * round up to the nearest second and sleep instead + */ + sleep((usec / 1000000) + 1); +#endif +} diff --git a/lib/isc/tests/isctest.h b/lib/isc/tests/isctest.h index 243e7feb15..068e5cbd10 100644 --- a/lib/isc/tests/isctest.h +++ b/lib/isc/tests/isctest.h @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: isctest.h,v 1.3 2011/07/28 04:04:37 each Exp $ */ +/* $Id: isctest.h,v 1.4 2011/09/02 21:15:38 each Exp $ */ /*! \file */ @@ -52,3 +52,5 @@ isc_test_begin(FILE *logfile, isc_boolean_t start_managers); void isc_test_end(void); +void +isc_test_nap(isc_uint32_t usec); diff --git a/lib/isc/tests/socket_test.c b/lib/isc/tests/socket_test.c index b7bdcfbf2a..501a3b8c4e 100644 --- a/lib/isc/tests/socket_test.c +++ b/lib/isc/tests/socket_test.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: socket_test.c,v 1.3 2011/07/28 23:47:59 tbox Exp $ */ +/* $Id: socket_test.c,v 1.4 2011/09/02 21:15:38 each Exp $ */ /*! \file */ @@ -56,22 +56,6 @@ event_done(isc_task_t *task, isc_event_t *event) { isc_event_free(&event); } -static void -nap(isc_uint32_t usec) { -#ifdef HAVE_NANOSLEEP - struct timespec ts; - - ts.tv_sec = usec / 1000000; - ts.tv_nsec = (usec % 1000000) * 1000; - nanosleep(&ts, NULL); -#elif HAVE_USLEEP - usleep(usec); -#else - /* Round up to the nearest second and sleep, instead */ - sleep((usec / 1000000) + 1); -#endif -} - static isc_result_t waitfor(completion_t *completion) { int i = 0; @@ -80,7 +64,7 @@ waitfor(completion_t *completion) { while (isc__taskmgr_ready(taskmgr)) isc__taskmgr_dispatch(taskmgr); #endif - nap(1000); + isc_test_nap(1000); } if (completion->done) return (ISC_R_SUCCESS); diff --git a/lib/isc/tests/task_test.c b/lib/isc/tests/task_test.c new file mode 100644 index 0000000000..34efe31c91 --- /dev/null +++ b/lib/isc/tests/task_test.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: task_test.c,v 1.2 2011/09/02 21:15:38 each Exp $ */ + +/*! \file */ + +#include + +#include + +#include + +#include +#include + +#include "../task_p.h" +#include "isctest.h" + +/* + * Helper functions + */ + +/* task event handler, sets a boolean to true */ +int counter = 0; +isc_mutex_t set_lock; + +static void +set(isc_task_t *task, isc_event_t *event) { + int *value = (int *) event->ev_arg; + + UNUSED(task); + + isc_event_free(&event); + LOCK(&set_lock); + *value = counter++; + UNLOCK(&set_lock); +} + +static void +set_and_drop(isc_task_t *task, isc_event_t *event) { + int *value = (int *) event->ev_arg; + + UNUSED(task); + + isc_event_free(&event); + LOCK(&set_lock); + *value = (int) isc_taskmgr_mode(taskmgr); + counter++; + UNLOCK(&set_lock); + isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_normal); +} + +/* + * Individual unit tests + */ + +/* Create a task */ +ATF_TC(create_task); +ATF_TC_HEAD(create_task, tc) { + atf_tc_set_md_var(tc, "descr", "create and destroy a task"); +} +ATF_TC_BODY(create_task, tc) { + isc_result_t result; + isc_task_t *task = NULL; + + UNUSED(tc); + + result = isc_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &task); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_task_destroy(&task); + ATF_REQUIRE_EQ(task, NULL); + + isc_test_end(); +} + +/* Process events */ +ATF_TC(all_events); +ATF_TC_HEAD(all_events, tc) { + atf_tc_set_md_var(tc, "descr", "process task events"); +} +ATF_TC_BODY(all_events, tc) { + isc_result_t result; + isc_task_t *task = NULL; + isc_event_t *event; + int a = 0, b = 0; + int i = 0; + + UNUSED(tc); + + counter = 1; + + result = isc_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &task); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* First event */ + event = isc_event_allocate(mctx, task, ISC_TASKEVENT_TEST, + set, &a, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(a, 0); + isc_task_send(task, &event); + + event = isc_event_allocate(mctx, task, ISC_TASKEVENT_TEST, + set, &b, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(b, 0); + isc_task_send(task, &event); + + while (a == 0 && b == 0 && i++ < 5000) { +#ifndef ISC_PLATFORM_USETHREADS + while (isc__taskmgr_ready(taskmgr)) + isc__taskmgr_dispatch(taskmgr); +#endif + isc_test_nap(1000); + } + + ATF_CHECK(a != 0); + ATF_CHECK(b != 0); + + isc_task_destroy(&task); + ATF_REQUIRE_EQ(task, NULL); + + isc_test_end(); +} + +/* Privileged events */ +ATF_TC(privileged_events); +ATF_TC_HEAD(privileged_events, tc) { + atf_tc_set_md_var(tc, "descr", "process privileged events"); +} +ATF_TC_BODY(privileged_events, tc) { + isc_result_t result; + isc_task_t *task1 = NULL, *task2 = NULL; + isc_event_t *event; + int a = 0, b = 0, c = 0, d = 0, e = 0; + int i = 0; + + UNUSED(tc); + + counter = 1; + result = isc_mutex_init(&set_lock); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + +#ifdef ISC_PLATFORM_USETHREADS + /* + * Pause the task manager so we can fill up the work queue + * without things happening while we do it. + */ + isc__taskmgr_pause(taskmgr); +#endif + + result = isc_task_create(taskmgr, 0, &task1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_task_setname(task1, "privileged", NULL); + ATF_CHECK(!isc_task_privilege(task1)); + isc_task_setprivilege(task1, ISC_TRUE); + ATF_CHECK(isc_task_privilege(task1)); + + result = isc_task_create(taskmgr, 0, &task2); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_task_setname(task2, "normal", NULL); + ATF_CHECK(!isc_task_privilege(task2)); + + /* First event: privileged */ + event = isc_event_allocate(mctx, task1, ISC_TASKEVENT_TEST, + set, &a, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(a, 0); + isc_task_send(task1, &event); + + /* Second event: not privileged */ + event = isc_event_allocate(mctx, task2, ISC_TASKEVENT_TEST, + set, &b, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(b, 0); + isc_task_send(task2, &event); + + /* Third event: privileged */ + event = isc_event_allocate(mctx, task1, ISC_TASKEVENT_TEST, + set, &c, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(c, 0); + isc_task_send(task1, &event); + + /* Fourth event: privileged */ + event = isc_event_allocate(mctx, task1, ISC_TASKEVENT_TEST, + set, &d, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(d, 0); + isc_task_send(task1, &event); + + /* Fifth event: not privileged */ + event = isc_event_allocate(mctx, task2, ISC_TASKEVENT_TEST, + set, &e, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(e, 0); + isc_task_send(task2, &event); + + ATF_CHECK_EQ(isc_taskmgr_mode(taskmgr), isc_taskmgrmode_normal); + isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_privileged); + ATF_CHECK_EQ(isc_taskmgr_mode(taskmgr), isc_taskmgrmode_privileged); + +#ifdef ISC_PLATFORM_USETHREADS + isc__taskmgr_resume(taskmgr); +#endif + + /* We're waiting for *all* variables to be set */ + while ((a == 0 || b == 0 || c == 0 || d == 0 || e == 0) && i++ < 5000) { +#ifndef ISC_PLATFORM_USETHREADS + while (isc__taskmgr_ready(taskmgr)) + isc__taskmgr_dispatch(taskmgr); +#endif + isc_test_nap(1000); + } + + /* + * We can't guarantee what order the events fire, but + * we do know the privileged tasks that set a, c, and d + * would have fired first. + */ + ATF_CHECK(a <= 3); + ATF_CHECK(c <= 3); + ATF_CHECK(d <= 3); + + /* ...and the non-privileged tasks that set b and e, last */ + ATF_CHECK(b >= 4); + ATF_CHECK(e >= 4); + + ATF_CHECK_EQ(counter, 6); + + isc_task_setprivilege(task1, ISC_FALSE); + ATF_CHECK(!isc_task_privilege(task1)); + + ATF_CHECK_EQ(isc_taskmgr_mode(taskmgr), isc_taskmgrmode_normal); + + isc_task_destroy(&task1); + ATF_REQUIRE_EQ(task1, NULL); + isc_task_destroy(&task2); + ATF_REQUIRE_EQ(task2, NULL); + + isc_test_end(); +} + +/* + * Edge case: this tests that the task manager behaves as expected when + * we explicitly set it into normal mode *while* running privileged. + */ +ATF_TC(privilege_drop); +ATF_TC_HEAD(privilege_drop, tc) { + atf_tc_set_md_var(tc, "descr", "process privileged events"); +} +ATF_TC_BODY(privilege_drop, tc) { + isc_result_t result; + isc_task_t *task1 = NULL, *task2 = NULL; + isc_event_t *event; + int a = 0, b = 0, c = 0, d = 0, e = 0; + int i = 0; + + UNUSED(tc); + + counter = 1; + result = isc_mutex_init(&set_lock); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + +#ifdef ISC_PLATFORM_USETHREADS + /* + * Pause the task manager so we can fill up the work queue + * without things happening while we do it. + */ + isc__taskmgr_pause(taskmgr); +#endif + + result = isc_task_create(taskmgr, 0, &task1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_task_setname(task1, "privileged", NULL); + ATF_CHECK(!isc_task_privilege(task1)); + isc_task_setprivilege(task1, ISC_TRUE); + ATF_CHECK(isc_task_privilege(task1)); + + result = isc_task_create(taskmgr, 0, &task2); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_task_setname(task2, "normal", NULL); + ATF_CHECK(!isc_task_privilege(task2)); + + /* First event: privileged */ + event = isc_event_allocate(mctx, task1, ISC_TASKEVENT_TEST, + set_and_drop, &a, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(a, 0); + isc_task_send(task1, &event); + + /* Second event: not privileged */ + event = isc_event_allocate(mctx, task2, ISC_TASKEVENT_TEST, + set_and_drop, &b, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(b, 0); + isc_task_send(task2, &event); + + /* Third event: privileged */ + event = isc_event_allocate(mctx, task1, ISC_TASKEVENT_TEST, + set_and_drop, &c, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(c, 0); + isc_task_send(task1, &event); + + /* Fourth event: privileged */ + event = isc_event_allocate(mctx, task1, ISC_TASKEVENT_TEST, + set_and_drop, &d, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(d, 0); + isc_task_send(task1, &event); + + /* Fifth event: not privileged */ + event = isc_event_allocate(mctx, task2, ISC_TASKEVENT_TEST, + set_and_drop, &e, sizeof (isc_event_t)); + ATF_REQUIRE(event != NULL); + + ATF_CHECK_EQ(e, 0); + isc_task_send(task2, &event); + + ATF_CHECK_EQ(isc_taskmgr_mode(taskmgr), isc_taskmgrmode_normal); + isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_privileged); + ATF_CHECK_EQ(isc_taskmgr_mode(taskmgr), isc_taskmgrmode_privileged); + +#ifdef ISC_PLATFORM_USETHREADS + isc__taskmgr_resume(taskmgr); +#endif + + /* We're waiting for *any* variable to be set */ + while ((a == 0 && b == 0 && c == 0 && d == 0 && e == 0) && i++ < 5000) { +#ifndef ISC_PLATFORM_USETHREADS + while (isc__taskmgr_ready(taskmgr)) + isc__taskmgr_dispatch(taskmgr); +#endif + isc_test_nap(1000); + } + + /* + * We can't guarantee what order the events fire, but + * we do know *exactly one* of the privileged tasks will + * have run in privileged mode... + */ + ATF_CHECK(a == isc_taskmgrmode_privileged || + c == isc_taskmgrmode_privileged || + d == isc_taskmgrmode_privileged); + ATF_CHECK(a + c + d == isc_taskmgrmode_privileged); + + /* ...and neither of the non-privileged tasks did... */ + ATF_CHECK(b == isc_taskmgrmode_normal || e == isc_taskmgrmode_normal); + + /* ...but all five of them did run. */ + ATF_CHECK_EQ(counter, 6); + + ATF_CHECK_EQ(isc_taskmgr_mode(taskmgr), isc_taskmgrmode_normal); + + isc_task_destroy(&task1); + ATF_REQUIRE_EQ(task1, NULL); + isc_task_destroy(&task2); + ATF_REQUIRE_EQ(task2, NULL); + + isc_test_end(); +} +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, create_task); + ATF_TP_ADD_TC(tp, all_events); + ATF_TP_ADD_TC(tp, privileged_events); + ATF_TP_ADD_TC(tp, privilege_drop); + + return (atf_no_error()); +} + diff --git a/lib/isc/tests/taskpool_test.c b/lib/isc/tests/taskpool_test.c index ab80f452bb..7b9f7ed455 100644 --- a/lib/isc/tests/taskpool_test.c +++ b/lib/isc/tests/taskpool_test.c @@ -14,7 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: taskpool_test.c,v 1.3 2011/07/28 04:04:37 each Exp $ */ +/* $Id: taskpool_test.c,v 1.4 2011/09/02 21:15:38 each Exp $ */ /*! \file */ @@ -148,6 +148,55 @@ ATF_TC_BODY(get_tasks, tc) { isc_test_end(); } +/* Get tasks */ +ATF_TC(set_privilege); +ATF_TC_HEAD(set_privilege, tc) { + atf_tc_set_md_var(tc, "descr", "create a taskpool"); +} +ATF_TC_BODY(set_privilege, tc) { + isc_result_t result; + isc_taskpool_t *pool = NULL; + isc_task_t *task1 = NULL, *task2 = NULL, *task3 = NULL; + + UNUSED(tc); + + result = isc_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_taskpool_create(taskmgr, mctx, 2, 2, &pool); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(isc_taskpool_size(pool), 2); + + isc_taskpool_setprivilege(pool, ISC_TRUE); + + isc_taskpool_gettask(pool, &task1); + isc_taskpool_gettask(pool, &task2); + isc_taskpool_gettask(pool, &task3); + + ATF_CHECK(ISCAPI_TASK_VALID(task1)); + ATF_CHECK(ISCAPI_TASK_VALID(task2)); + ATF_CHECK(ISCAPI_TASK_VALID(task3)); + + ATF_CHECK(isc_task_privilege(task1)); + ATF_CHECK(isc_task_privilege(task2)); + ATF_CHECK(isc_task_privilege(task3)); + + isc_taskpool_setprivilege(pool, ISC_FALSE); + + ATF_CHECK(!isc_task_privilege(task1)); + ATF_CHECK(!isc_task_privilege(task2)); + ATF_CHECK(!isc_task_privilege(task3)); + + isc_task_destroy(&task1); + isc_task_destroy(&task2); + isc_task_destroy(&task3); + + isc_taskpool_destroy(&pool); + ATF_REQUIRE_EQ(pool, NULL); + + isc_test_end(); +} + /* * Main */ @@ -155,6 +204,7 @@ ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, create_pool); ATF_TP_ADD_TC(tp, expand_pool); ATF_TP_ADD_TC(tp, get_tasks); + ATF_TP_ADD_TC(tp, set_privilege); return (atf_no_error()); } diff --git a/lib/isc/win32/libisc.def b/lib/isc/win32/libisc.def index 226e014238..fb084103da 100644 --- a/lib/isc/win32/libisc.def +++ b/lib/isc/win32/libisc.def @@ -127,17 +127,21 @@ isc__task_getcurrenttime isc__task_getname isc__task_gettag isc__task_onshutdown +isc__task_privilege isc__task_purge isc__task_purgeevent isc__task_purgerange isc__task_send isc__task_sendanddetach isc__task_setname +isc__task_setprivilege isc__task_shutdown isc__task_unsend isc__task_unsendrange isc__taskmgr_create isc__taskmgr_destroy +isc__taskmgr_mode +isc__taskmgr_setmode isc__timer_attach isc__timer_create isc__timer_detach @@ -535,6 +539,7 @@ isc_taskpool_create isc_taskpool_destroy isc_taskpool_expand isc_taskpool_gettask +isc_taskpool_setprivilege isc_taskpool_size isc_thread_create isc_thread_join diff --git a/make/rules.in b/make/rules.in index ed8b84303d..7992d682ce 100644 --- a/make/rules.in +++ b/make/rules.in @@ -13,7 +13,7 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: rules.in,v 1.70 2011/03/01 23:48:07 tbox Exp $ +# $Id: rules.in,v 1.71 2011/09/02 21:15:39 each Exp $ ### ### Common Makefile rules for BIND 9. @@ -88,7 +88,7 @@ testdirs: install:: all install clean distclean maintainer-clean doc docclean man manclean:: - @for i in ${ALL_SUBDIRS}; do \ + @for i in ${ALL_SUBDIRS} ${ALL_TESTDIRS}; do \ if [ "$$i" != "nulldir" -a -d $$i ]; then \ echo "making $@ in `pwd`/$$i"; \ (cd $$i; ${MAKE} ${MAKEDEFS} DESTDIR="${DESTDIR}" $@) || exit 1; \