summaryrefslogtreecommitdiffstats
path: root/tdecore/network/kresolvermanager.cpp
blob: 7801a42958014f62e7cdae2c7ab5687672120cb1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
/*  -*- C++ -*-
 *  Copyright (C) 2003-2005 Thiago Macieira <[email protected]>
 *
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included 
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "config.h"

#include <sys/types.h>
#include <netinet/in.h>
#include <limits.h>
#include <unistd.h>		// only needed for pid_t

#ifdef HAVE_RES_INIT
# include <sys/stat.h>
extern "C" {
#   include <arpa/nameser.h>
}
# include <time.h>
# include <resolv.h>
#endif

#include <tqapplication.h>
#include <tqstring.h>
#include <tqcstring.h>
#include <tqptrlist.h>
#include <tqtimer.h>
#include <tqmutex.h>
#include <tqthread.h>
#include <tqwaitcondition.h>
#include <tqsemaphore.h>

#include <kde_file.h>
#include <kdebug.h>
#include "kresolver.h"
#include "kresolver_p.h"
#include "kresolverworkerbase.h"

namespace KNetwork
{
  namespace Internal
  {
    void initSrvWorker();
    void initStandardWorkers();
  }
}

using namespace KNetwork;
using namespace KNetwork::Internal;

/*
 * Explanation on how the resolver system works

   When KResolver::start is called, it calls KResolverManager::enqueue to add
   an entry to the queue. KResolverManager::enqueue will verify the availability
   of a worker thread: if one is available, it will dispatch the request to it.
   If no threads are available, it will then decide whether to launch a thread
   or to queue for the future.

   (This process is achieved by always queueing the new request, starting a
   new thread if necessary and then notifying of the availability of data
   to all worker threads).

 * Worker thread
   A new thread, when started, will enter its event loop
   immediately. That is, it'll first try to acquire new data to
   process, which means it will lock and unlock the manager mutex in
   the process.

   If it finds no new data, it'll wait on the feedWorkers condition
   for a certain maximum time. If that time expires and there's still
   no data, the thread will exit, in order to save system resources.

   If it finds data, however, it'll set up and call the worker class
   that has been selected by the manager. Once that worker is done,
   the thread releases the data through KResolverManager::releaseData.

 * Data requesting/releasing
   A worker thread always calls upon functions on the resolver manager
   in order to acquire and release data.

   When data is being requested, the KResolverManager::requestData
   function will look the currentRequests list and return the first
   Queued request it finds, while marking it to be InProgress.

   When the worker class has returned, the worker thread will release
   that data through the KResolverManager::releaseData function. If the
   worker class has requested no further data (nRequests == 0), the
   request's status is marked to be Done. It'll then look at the
   requestor for that data: if it was requested by another worker,
   it'll decrement the requests count for that one and add the results
   to a list. And, finally, if the requests count for the requestor
   becomes 0, it'll repeat this process for the requestor as well
   (change status to Done, check for a requestor).
 */

namespace
{

/*
 * This class is used to control the access to the
 * system's resolver API.
 *
 * It is necessary to periodically poll /etc/resolv.conf and reload
 * it if any changes are noticed. This class does exactly that.
 *
 * However, there's also the problem of reloading the structure while
 * some threads are in progress. Therefore, we keep a usage reference count.
 */
class ResInitUsage
{
public:

#ifdef HAVE_RES_INIT
  time_t mTime;
  int useCount;

# ifndef RES_INIT_THREADSAFE
  TQWaitCondition cond;
  TQMutex mutex;
# endif

  bool shouldResInit()
  {
    // check if /etc/resolv.conf has changed 
    KDE_struct_stat st;
    if (KDE_stat("/etc/resolv.conf", &st) != 0)
      return false;
    
    if (mTime != st.st_mtime)
      {
	kdDebug(179) << "shouldResInit: /etc/resolv.conf updated" << endl;
	return true;
      }
    return false;
  }

  void callResInit()
  {
    if (mTime != 0)
      {
	// don't call it the first time
	// let it be initialised naturally
	kdDebug(179) << "callResInit: calling res_init()" << endl;
	res_init();
      }
    
    KDE_struct_stat st;
    if (KDE_stat("/etc/resolv.conf", &st) == 0)
      mTime = st.st_mtime;
  }

  ResInitUsage()
    : mTime(0), useCount(0)
  { }

  /*
   * Marks the end of usage to the resolver tools
   */
  void release()
  {
# ifndef RES_INIT_THREADSAFE
    TQMutexLocker locker(&mutex);
    if (--useCount == 0)
      {
	if (shouldResInit())
	  callResInit();

	// we've reached 0, wake up anyone that's waiting to call res_init
	cond.wakeAll();
      }
# else
    // do nothing
# endif
  }

  /*
   * Marks the beginning of usage of the resolver API
   */
  void acquire()
  {
# ifndef RES_INIT_THREADSAFE
    mutex.lock();

    if (shouldResInit())
      {
	if (useCount)
	  {
	    // other threads are already using the API, so wait till
	    // it's all clear
	    // the thread that emits this condition will also call res_init
	    //tqDebug("ResInitUsage: waiting for libresolv to be clear");
	    cond.wait(&mutex);
	  }
	else
	  // we're clear
	  callResInit();
      }
    useCount++;
    mutex.unlock();

# else
    if (shouldResInit())
      callResInit();

# endif
  }

#else
  ResInitUsage()
  { }

  bool shouldResInit()
  { return false; }

  void acquire()
  { }

  void release()
  { }
#endif

} resInit;

} // anonymous namespace

/*
 * parameters
 */
// a thread will try maxThreadRetries to get data, waiting at most
// maxThreadWaitTime milliseconds between each attempt. After that, it'll
// exit
static const int maxThreadWaitTime = 2000; // 2 seconds
static const int maxThreads = 5;

static pid_t pid;		// FIXME -- disable when everything is ok

KResolverThread::KResolverThread()
  : data(0L)
{
}

// remember! This function runs in a separate thread!
void KResolverThread::run()
{
  // initialisation
  // enter the loop already

  //tqDebug("KResolverThread(thread %u/%p): started", pid, (void*)TQThread::currentThread());
  KResolverManager::manager()->registerThread(this);
  while (true)
    {
      data = KResolverManager::manager()->requestData(this, ::maxThreadWaitTime);
      //tqDebug("KResolverThread(thread %u/%p) got data %p", KResolverManager::pid, 
      //       (void*)TQThread::currentThread(), (void*)data);
      if (data)
	{
	  // yes, we got data
	  // process it!
      
	  // 1) set up
	  ;
      
	  // 2) run it
	  data->worker->run();
	  
	  // 3) release data
	  KResolverManager::manager()->releaseData(this, data);
	  
	  // now go back to the loop
	}
      else
	break;
    }

  KResolverManager::manager()->unregisterThread(this);
  //tqDebug("KResolverThread(thread %u/%p): exiting", pid, (void*)TQThread::currentThread());
}

bool KResolverThread::checkResolver()
{
  return resInit.shouldResInit();
}

void KResolverThread::acquireResolver()
{
#if defined(NEED_MUTEX) && !defined(Q_OS_FREEBSD)
  getXXbyYYmutex.lock();
#endif

  resInit.acquire();
}

void KResolverThread::releaseResolver()
{
#if defined(NEED_MUTEX) && !defined(Q_OS_FREEBSD)
  getXXbyYYmutex.unlock();
#endif

  resInit.release();
}

static KResolverManager *globalManager;

KResolverManager* KResolverManager::manager()
{
  if (globalManager == 0L)
    new KResolverManager();
  return globalManager;
}

KResolverManager::KResolverManager()
  : runningThreads(0), availableThreads(0)
{
  globalManager = this;
  workers.setAutoDelete(true);
  currentRequests.setAutoDelete(true);
  initSrvWorker();
  initStandardWorkers();

  pid = getpid();
}

KResolverManager::~KResolverManager()
{
  // this should never be called

  // kill off running threads
  for (workers.first(); workers.current(); workers.next())
    workers.current()->terminate();
}

void KResolverManager::registerThread(KResolverThread* )
{
}

void KResolverManager::unregisterThread(KResolverThread*)
{
  runningThreads--;
}

// this function is called by KResolverThread::run
RequestData* KResolverManager::requestData(KResolverThread *th, int maxWaitTime)
{
  /////
  // This function is called in a worker thread!!
  /////

  // lock the mutex, so that the manager thread or other threads won't
  // interfere.
  TQMutexLocker locker(&mutex);
  RequestData *data = findData(th);

  if (data)
    // it found something, that's good
    return data;

  // nope, nothing found; sleep for a while
  availableThreads++;
  feedWorkers.wait(&mutex, maxWaitTime);
  availableThreads--;

  data = findData(th);
  return data;
}

RequestData* KResolverManager::findData(KResolverThread* th)
{
  /////
  // This function is called by @ref requestData above and must
  // always be called with a locked mutex
  /////

  // now find data to be processed
  for (RequestData *curr = newRequests.first(); curr; curr = newRequests.next())
    if (!curr->worker->m_finished)
      {
	// found one
	if (curr->obj)
	  curr->obj->status = KResolver::InProgress;
	curr->worker->th = th;

	// move it to the currentRequests list
	currentRequests.append(newRequests.take());

	return curr;
      }

  // found nothing!
  return 0L;
}

// this function is called by KResolverThread::run
void KResolverManager::releaseData(KResolverThread *, RequestData* data)
{
  /////
  // This function is called in a worker thread!!
  /////

  //tqDebug("KResolverManager::releaseData(%u/%p): %p has been released", pid, 
//	 (void*)TQThread::currentThread(), (void*)data);

  if (data->obj)
    {
      data->obj->status = KResolver::PostProcessing;	
    }
      
  data->worker->m_finished = true;
  data->worker->th = 0L;	// this releases the object

  // handle finished requests
  handleFinished();
}

// this function is called by KResolverManager::releaseData above
void KResolverManager::handleFinished()
{  
  bool redo = false;
  TQPtrQueue<RequestData> doneRequests;

  mutex.lock();

  // loop over all items on the currently running list
  // we loop from the last to the first so that we catch requests with "requestors" before
  // we catch the requestor itself.
  RequestData *curr = currentRequests.last();
  while (curr)
    {
      if (curr->worker->th == 0L)
	{
	  if (handleFinishedItem(curr))
	    {
	      doneRequests.enqueue(currentRequests.take());
	      if (curr->requestor &&
		  curr->requestor->nRequests == 0 && 
		  curr->requestor->worker->m_finished)
		// there's a requestor that is now finished
		redo = true;
	    }
	}
      
      curr = currentRequests.prev();
    }
      
  //tqDebug("KResolverManager::handleFinished(%u): %d requests to notify", pid, doneRequests.count());
  while (RequestData *d = doneRequests.dequeue())
    doNotifying(d);

  mutex.unlock();

  if (redo)
    {
      //tqDebug("KResolverManager::handleFinished(%u): restarting processing to catch requestor",
	//     pid);
      handleFinished();
    }
}

// This function is called by KResolverManager::handleFinished above
bool KResolverManager::handleFinishedItem(RequestData* curr)
					  
{
  // for all items that aren't currently running, remove from the list
  // this includes all finished or cancelled requests

  if (curr->worker->m_finished && curr->nRequests == 0)
    {
      // this one has finished
      if (curr->obj)
	curr->obj->status = KResolver::PostProcessing; // post-processing is run in doNotifying()

      if (curr->requestor)
	--curr->requestor->nRequests;

      //tqDebug("KResolverManager::handleFinishedItem(%u): removing %p since it's done",
	//     pid, (void*)curr);
      return true;
    }
  return false;
}



void KResolverManager::registerNewWorker(KResolverWorkerFactoryBase *factory)
{
  workerFactories.append(factory);
}

KResolverWorkerBase* KResolverManager::findWorker(KResolverPrivate* p)
{
  /////
  // this function can be called on any user thread
  /////

  // this function is called with an unlocked mutex and it's expected to be 
  // thread-safe!
  // but the factory list is expected not to be changed asynchronously

  // This function is responsible for finding a suitable worker for the given
  // input. That means we have to do a costly operation to create each worker
  // class and call their preprocessing functions. The first one that
  // says they can process (i.e., preprocess() returns true) will get the job.

  KResolverWorkerBase *worker;
  for (KResolverWorkerFactoryBase *factory = workerFactories.first(); factory; 
       factory = workerFactories.next())
    {
      worker = factory->create();

      // set up the data the worker needs to preprocess
      worker->input = &p->input;

      if (worker->preprocess())
	{
	  // good, this one says it can process
	  if (worker->m_finished)	   
	    p->status = KResolver::PostProcessing;
	  else
	    p->status = KResolver::Queued;
	  return worker;
	}

      // no, try again
      delete worker;
    }

  // found no worker
  return 0L;
}

void KResolverManager::doNotifying(RequestData *p)
{
  /////
  // This function may be called on any thread
  // any thread at all: user threads, GUI thread, manager thread or worker thread
  /////

  // Notification and finalisation
  //
  // Once a request has finished the normal processing, we call the
  // post processing function.
  //
  // After that is done, we will consolidate all results in the object's
  // KResolverResults and then post an event indicating that the signal
  // be emitted
  //
  // In case we detect that the object is waiting for completion, we do not
  // post the event, for KResolver::wait will take care of emitting the
  // signal.
  //
  // Once we release the mutex on the object, we may no longer reference it
  // for it might have been deleted.

  // "User" objects are those that are not created by the manager. Note that
  // objects created by worker threads are considered "user" objects. Objects
  // created by the manager are those created for KResolver::resolveAsync.
  // We should delete them.

  if (p->obj)
    {
      // lock the object
      p->obj->mutex.lock();
      KResolver* parent = p->obj->parent; // is 0 for non-"user" objects
      KResolverResults& r = p->obj->results;

      if (p->obj->status == KResolver::Canceled)
	{
	  p->obj->status = KResolver::Canceled;
	  p->obj->errorcode = KResolver::Canceled;
	  p->obj->syserror = 0;
	  r.setError(KResolver::Canceled, 0);
	}
      else if (p->worker)
	{
	  // post processing
	  p->worker->postprocess();	// ignore the result

	  // copy the results from the worker thread to the final
	  // object
	  r = p->worker->results;

	  // reset address
	  r.setAddress(p->input->node, p->input->service);

	  //tqDebug("KResolverManager::doNotifying(%u/%p): for %p whose status is %d and has %d results", 
		 //pid, (void*)TQThread::currentThread(), (void*)p, p->obj->status, r.count());

	  p->obj->errorcode = r.error();
	  p->obj->syserror = r.systemError();
	  p->obj->status = !r.isEmpty() ? 
	    KResolver::Success : KResolver::Failed;
	}
      else
	{
	  r.empty();
	  r.setError(p->obj->errorcode, p->obj->syserror);
	}

      // check whether there's someone waiting
      if (!p->obj->waiting && parent)
	// no, so we must post an event requesting that the signal be emitted
	// sorry for the C-style cast, but neither static nor reintepret cast work
	// here; I'd have to do two casts
	TQApplication::postEvent(parent, new TQEvent((TQEvent::Type)(ResolutionCompleted)));

      // release the mutex
      p->obj->mutex.unlock();
    }
  else
    {
      // there's no object!
      if (p->worker)
	p->worker->postprocess();
    }

  delete p->worker;

  // ignore p->requestor and p->nRequests
  // they have been dealt with by the main loop

  delete p;

  // notify any objects waiting in KResolver::wait
  notifyWaiters.wakeAll();
}

// enqueue a new request
// this function is called from KResolver::start and 
// from KResolverWorkerBase::enqueue
void KResolverManager::enqueue(KResolver *obj, RequestData *requestor)
{
  RequestData *newrequest = new RequestData;
  newrequest->nRequests = 0;
  newrequest->obj = obj->d;
  newrequest->input = &obj->d->input;
  newrequest->requestor = requestor;

  // when processing a new request, find the most
  // suitable worker
  if ((newrequest->worker = findWorker(obj->d)) == 0L)
    {
      // oops, problem
      // cannot find a worker class for this guy
      obj->d->status = KResolver::Failed;
      obj->d->errorcode = KResolver::UnsupportedFamily;
      obj->d->syserror = 0;

      doNotifying(newrequest);
      return;
    }

  // no, queue it
  // p->status was set in findWorker!
  if (requestor)
    requestor->nRequests++;

  if (!newrequest->worker->m_finished)
    dispatch(newrequest);
  else if (newrequest->nRequests > 0)
    {
      mutex.lock();
      currentRequests.append(newrequest);
      mutex.unlock();
    }
  else
    // already done
    doNotifying(newrequest);
}

// a new request has been created
// dispatch it
void KResolverManager::dispatch(RequestData *data)
{
  // As stated in the beginning of the file, this function
  // is supposed to verify the availability of threads, start
  // any if necessary

  TQMutexLocker locker(&mutex);

  // add to the queue
  newRequests.append(data);

  // check if we need to start a new thread
  //
  // we depend on the variables availableThreads and runningThreads to
  // know if we are supposed to start any threads:
  // - if availableThreads > 0, then there is at least one thread waiting,
  //    blocked in KResolverManager::requestData. It can't unblock
  //    while we are holding the mutex locked, therefore we are sure that
  //    our event will be handled
  // - if availableThreads == 0:
  //   - if runningThreads < maxThreads
  //     we will start a new thread, which will certainly block in
  //     KResolverManager::requestData because we are holding the mutex locked
  //   - if runningThreads == maxThreads
  //     This situation generally means that we have already maxThreads running
  //     and that all of them are processing. We will not start any new threads,
  //     but will instead wait for one to finish processing and request new data
  //
  //     There's a possible race condition here, which goes unhandled: if one of
  //     threads has timed out waiting for new data and is in the process of
  //     exiting. In that case, availableThreads == 0 and runningThreads will not
  //     have decremented yet. This means that we will not start a new thread
  //     that we could have. However, since there are other threads working, our
  //     event should be handled soon.
  //     It won't be handled if and only if ALL threads are in the process of 
  //     exiting. That situation is EXTREMELY unlikely and is not handled either.
  //
  if (availableThreads == 0 && runningThreads < maxThreads)
    {
      // yes, a new thread should be started

      // find if there's a finished one
      KResolverThread *th = workers.first();
      while (th && th->running())
	th = workers.next();

      if (th == 0L)
	// no, create one
	th = new KResolverThread;
      else
	workers.take();

      th->start();
      workers.append(th);
      runningThreads++;
    }

  feedWorkers.wakeAll();

  // clean up idle threads
  workers.first();
  while (workers.current())
    {
      if (!workers.current()->running())
	workers.remove();
      else
	workers.next();
    }
}

// this function is called by KResolverManager::dequeue
bool KResolverManager::dequeueNew(KResolver* obj)
{
  // This function must be called with a locked mutex
  // Deadlock warning:
  // always lock the global mutex first if both mutexes must be locked

  KResolverPrivate *d = obj->d;

  // check if it's in the new request list
  RequestData *curr = newRequests.first(); 
  while (curr)
    if (curr->obj == d)
      {
	// yes, this object is still in the list
	// but it has never been processed
	d->status = KResolver::Canceled;
	d->errorcode = KResolver::Canceled;
	d->syserror = 0;
	newRequests.take();

	delete curr->worker;
	delete curr;
	
	return true;
      }
    else
      curr = newRequests.next();

  // check if it's running
  curr = currentRequests.first();
  while (curr)
    if (curr->obj == d)
      {
	// it's running. We cannot simply take it out of the list.
	// it will be handled when the thread that is working on it finishes
	d->mutex.lock();

	d->status = KResolver::Canceled;
	d->errorcode = KResolver::Canceled;
	d->syserror = 0;

	// disengage from the running threads
	curr->obj = 0L;
	curr->input = 0L;
	if (curr->worker)
	  curr->worker->input = 0L;

	d->mutex.unlock();
      }
    else
      curr = currentRequests.next();

  return false;
}

// this function is called by KResolver::cancel
// it's expected to be thread-safe
void KResolverManager::dequeue(KResolver *obj)
{
  TQMutexLocker locker(&mutex);
  dequeueNew(obj);
}