2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
20 // machserver - C++ shell for writing Mach 3 servers
22 #include "machserver.h"
23 #include <servers/bootstrap.h>
24 #include <mach/kern_return.h>
25 #include <mach/message.h>
26 #include <mach/mig_errors.h>
27 #include "mach_notify.h"
28 #include <Security/debugging.h>
30 #if defined(USECFCURRENTTIME)
31 # include <CoreFoundation/CFDate.h>
33 # include <sys/time.h>
37 namespace MachPlusPlus
{
41 // Global per-thread information
43 ModuleNexus
< ThreadNexus
<MachServer::PerThread
> > MachServer::thread
;
47 // Create a server object.
48 // The resulting object is not "active", and any number of server objects
49 // can be in this "prepared" state at the same time.
51 MachServer::MachServer(const char *name
)
52 : mServerPort(name
, bootstrap
)
55 MachServer::MachServer(const char *name
, const Bootstrap
&boot
)
56 : bootstrap(boot
), mServerPort(name
, bootstrap
)
59 void MachServer::setup(const char *name
)
61 debug("machsrv", "%p preparing service for \"%s\"", this, name
);
62 workerTimeout
= 60 * 2; // 2 minutes default timeout
63 maxWorkerCount
= 100; // sanity check limit
65 mPortSet
+= mServerPort
;
68 MachServer::~MachServer()
70 // The ReceivePort members will clean themselves up.
71 // The bootstrap server will clear us from its map when our receive port dies.
72 debug("machsrv", "%p destroyed", this);
77 // Add and remove extra listening ports.
78 // Messages directed to those ports are dispatched through the main handler.
79 // To get automatic call-out to another handler, use the Handler class.
81 void MachServer::add(Port receiver
)
83 debug("machsrv", "adding port %d to primary dispatch", receiver
.port());
87 void MachServer::remove(Port receiver
)
89 debug("machsrv", "removing port %d from primary dispatch", receiver
.port());
95 // Register for mach port notifications
97 void MachServer::notifyIfDead(Port port
, bool doNotify
) const
100 port
.requestNotify(mServerPort
, MACH_NOTIFY_DEAD_NAME
, true);
102 port
.cancelNotify(MACH_NOTIFY_DEAD_NAME
);
105 void MachServer::notifyIfUnused(Port port
, bool doNotify
) const
108 port
.requestNotify(port
, MACH_NOTIFY_NO_SENDERS
, true);
110 port
.cancelNotify(MACH_NOTIFY_NO_SENDERS
);
116 // This call will take control of the current thread and use it to service
117 // incoming requests. The thread will not be released until an error happens, which
118 // will cause an exception to be thrown. In other words, this never returns normally.
119 // We may also be creating additional threads to service concurrent requests
121 // @@@ Msg-errors in additional threads are not acted upon.
123 void MachServer::run(size_t maxSize
, mach_msg_options_t options
)
125 // establish server-global (thread-shared) parameters
127 mMsgOptions
= options
;
129 // establish the thread pool state
130 // (don't need managerLock since we're the only thread as of yet)
131 idleCount
= workerCount
= 1;
132 nextCheckTime
= Time::now() + workerTimeout
;
133 leastIdleWorkers
= 1;
134 highestWorkerCount
= 1;
136 // run server loop in initial (immortal) thread
137 runServerThread(false);
139 // primary server thread exited somehow (not currently possible)
145 // This is the core of a server thread at work. It takes over the thread until
146 // (a) an error occurs, throwing an exception
147 // (b) low-load timeout happens, causing a normal return (doTimeout only)
148 // This code is loosely based on mach_msg_server.c, but is drifting away for
149 // various reasons of flexibility and resilience.
151 extern "C" boolean_t
cdsa_notify_server(mach_msg_header_t
*in
, mach_msg_header_t
*out
);
153 void MachServer::runServerThread(bool doTimeout
)
155 // allocate request/reply buffers
156 Message
bufRequest(mMaxSize
);
157 Message
bufReply(mMaxSize
);
159 // all exits from runServerThread are through exceptions
161 // register as a worker thread
162 debug("machsrv", "%p starting service on port %d", this, int(mServerPort
));
163 perThread().server
= this;
166 // process all pending timers
167 while (processTimer()) ;
169 // check for worker idle timeout
170 { StLock
<Mutex
> _(managerLock
);
171 // record idle thread low-water mark in scan interval
172 if (idleCount
< leastIdleWorkers
)
173 leastIdleWorkers
= idleCount
;
175 // perform self-timeout processing
177 if (workerCount
> maxWorkerCount
) {
178 debug("machsrv", "%p too many threads; reaping immediately", this);
181 Time::Absolute rightNow
= Time::now();
182 if (rightNow
>= nextCheckTime
) { // reaping period complete; process
183 uint32 idlers
= leastIdleWorkers
;
184 debug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total",
185 this, idlers
, workerCount
);
186 nextCheckTime
= rightNow
+ workerTimeout
;
187 leastIdleWorkers
= INT_MAX
;
194 // release deferred-release memory
195 releaseDeferredAllocations();
197 // determine next timeout, or zero for infinity
198 bool indefinite
= false;
199 Time::Interval timeout
;
200 { StLock
<Mutex
> _(managerLock
);
201 if (timers
.empty()) {
203 timeout
= workerTimeout
;
208 ? min(workerTimeout
, timers
.next() - Time::now())
209 : timers
.next() - Time::now();
213 // receive next IPC request (or wait for timeout)
214 switch (mach_msg_return_t mr
= indefinite
?
215 mach_msg_overwrite(bufRequest
,
216 MACH_RCV_MSG
| mMsgOptions
,
217 0, mMaxSize
, mPortSet
,
218 MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
,
219 (mach_msg_header_t
*) 0, 0)
221 mach_msg_overwrite(bufRequest
,
222 MACH_RCV_MSG
| MACH_RCV_TIMEOUT
| mMsgOptions
,
223 0, mMaxSize
, mPortSet
,
224 mach_msg_timeout_t(timeout
.mSeconds()), MACH_PORT_NULL
,
225 (mach_msg_header_t
*) 0, 0)) {
226 case MACH_MSG_SUCCESS
:
227 // process received request message below
229 case MACH_RCV_TIMED_OUT
:
230 // back to top for time-related processing
232 case MACH_RCV_TOO_LARGE
:
233 // the kernel destroyed the request
235 case MACH_RCV_INTERRUPTED
:
236 // receive interrupted, try again
242 // process received message
243 if (bufRequest
.msgId() >= MACH_NOTIFY_FIRST
&&
244 bufRequest
.msgId() <= MACH_NOTIFY_LAST
) {
245 // mach kernel notification message
246 // we assume this is quick, so no thread arbitration here
247 cdsa_notify_server(bufRequest
, bufReply
);
249 // normal request message
250 { StLock
<Mutex
> _(managerLock
); idleCount
--; }
252 "servicing port %d request id=%d",
253 bufRequest
.localPort().port(), bufRequest
.msgId());
255 // try subsidiary handlers first
256 bool handled
= false;
257 for (HandlerSet::const_iterator it
= mHandlers
.begin();
258 it
!= mHandlers
.end(); it
++)
259 if (bufRequest
.localPort() == (*it
)->port()) {
260 (*it
)->handle(bufRequest
, bufReply
);
264 // unclaimed, send to main handler
265 handle(bufRequest
, bufReply
);
268 debug("machsrvreq", "request complete");
269 { StLock
<Mutex
> _(managerLock
); idleCount
++; }
272 // process reply generated by handler
273 if (!(bufReply
.bits() & MACH_MSGH_BITS_COMPLEX
) &&
274 bufReply
.returnCode() != KERN_SUCCESS
) {
275 if (bufReply
.returnCode() == MIG_NO_REPLY
)
277 // don't destroy the reply port right, so we can send an error message
278 bufRequest
.remotePort(MACH_PORT_NULL
);
279 mach_msg_destroy(bufRequest
);
282 if (bufReply
.remotePort() == MACH_PORT_NULL
) {
283 // no reply port, so destroy the reply
284 if (bufReply
.bits() & MACH_MSGH_BITS_COMPLEX
)
290 * We don't want to block indefinitely because the client
291 * isn't receiving messages from the reply port.
292 * If we have a send-once right for the reply port, then
293 * this isn't a concern because the send won't block.
294 * If we have a send right, we need to use MACH_SEND_TIMEOUT.
295 * To avoid falling off the kernel's fast RPC path unnecessarily,
296 * we only supply MACH_SEND_TIMEOUT when absolutely necessary.
298 switch (mach_msg_return_t mr
= mach_msg_overwrite(bufReply
,
299 (MACH_MSGH_BITS_REMOTE(bufReply
.bits()) ==
300 MACH_MSG_TYPE_MOVE_SEND_ONCE
) ?
301 MACH_SEND_MSG
| mMsgOptions
:
302 MACH_SEND_MSG
| MACH_SEND_TIMEOUT
| mMsgOptions
,
303 bufReply
.length(), 0, MACH_PORT_NULL
,
304 0, MACH_PORT_NULL
, NULL
, 0)) {
305 case MACH_MSG_SUCCESS
:
307 case MACH_SEND_INVALID_DEST
:
308 case MACH_SEND_TIMED_OUT
:
309 /* the reply can't be delivered, so destroy it */
310 mach_msg_destroy(bufRequest
);
316 perThread().server
= NULL
;
317 debug("machsrv", "%p ending service on port %d", this, int(mServerPort
));
320 perThread().server
= NULL
;
321 debug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort
));
328 // Manage subsidiary ports
330 void MachServer::add(Handler
&handler
)
332 assert(mHandlers
.find(&handler
) == mHandlers
.end());
333 assert(handler
.port() != MACH_PORT_NULL
);
334 mHandlers
.insert(&handler
);
335 mPortSet
+= handler
.port();
338 void MachServer::remove(Handler
&handler
)
340 assert(mHandlers
.find(&handler
) != mHandlers
.end());
341 mHandlers
.erase(&handler
);
342 mPortSet
-= handler
.port();
347 // Implement a Handler that sends no reply
349 boolean_t
MachServer::NoReplyHandler::handle(mach_msg_header_t
*in
, mach_msg_header_t
*out
)
351 // set up reply message to be valid (enough) and read "do not send reply"
353 out
->msgh_remote_port
= MACH_PORT_NULL
;
354 out
->msgh_size
= sizeof(mig_reply_error_t
);
355 ((mig_reply_error_t
*)out
)->RetCode
= MIG_NO_REPLY
;
357 // call input-only handler
363 // Register a memory block for deferred release.
365 void MachServer::releaseWhenDone(CssmAllocator
&alloc
, void *memory
)
368 set
<Allocation
> &releaseSet
= perThread().deferredAllocations
;
369 assert(releaseSet
.find(Allocation(memory
, alloc
)) == releaseSet
.end());
370 debug("machsrvmem", "%p register %p for release with %p",
371 this, memory
, &alloc
);
372 releaseSet
.insert(Allocation(memory
, alloc
));
378 // Run through the accumulated deferred allocations and release them.
379 // This is done automatically on every pass through the server loop;
380 // it must be called by subclasses that implement their loop in some
382 // @@@X Needs to be thread local
384 void MachServer::releaseDeferredAllocations()
386 set
<Allocation
> &releaseSet
= perThread().deferredAllocations
;
387 for (set
<Allocation
>::iterator it
= releaseSet
.begin(); it
!= releaseSet
.end(); it
++) {
388 debug("machsrvmem", "%p release %p with %p", this, it
->addr
, it
->allocator
);
389 it
->allocator
->free(it
->addr
);
391 releaseSet
.erase(releaseSet
.begin(), releaseSet
.end());
396 // The handler function calls this if it realizes that it might be blocked
397 // (or doing something that takes a long time). We respond by ensuring that
398 // at least one more thread is ready to serve requests.
400 void MachServer::longTermActivity()
402 StLock
<Mutex
> _(managerLock
);
403 if (idleCount
== 0 && workerCount
< maxWorkerCount
) {
404 // spawn a new thread of activity that shares in the server main loop
405 (new LoadThread(*this))->run();
409 void MachServer::LoadThread::action()
411 //@@@ race condition?! can server exit before helpers thread gets here?
413 // register the worker thread and go
414 server
.addThread(this);
416 server
.runServerThread(true);
418 // fell out of server loop by error. Let the thread go quietly
420 server
.removeThread(this);
423 void MachServer::addThread(Thread
*thread
)
425 StLock
<Mutex
> _(managerLock
);
428 debug("machsrv", "%p adding worker thread (%ld workers, %ld idle)",
429 this, workerCount
, idleCount
);
430 workers
.insert(thread
);
433 void MachServer::removeThread(Thread
*thread
)
435 StLock
<Mutex
> _(managerLock
);
438 debug("machsrv", "%p removing worker thread (%ld workers, %ld idle)",
439 this, workerCount
, idleCount
);
440 workers
.erase(thread
);
447 bool MachServer::processTimer()
450 { StLock
<Mutex
> _(managerLock
); // could have multiple threads trying this
451 if (!(top
= static_cast<Timer
*>(timers
.pop(Time::now()))))
452 return false; // nothing (more) to be done now
453 } // drop lock; work has been retrieved
454 debug("machsrvtime", "%p timer %p executing at %.3f",
455 this, top
, Time::now().internalForm());
458 debug("machsrvtime", "%p timer %p done", this, top
);
460 debug("machsrvtime", "%p server timer %p failed with exception", this, top
);
465 void MachServer::setTimer(Timer
*timer
, Time::Absolute when
)
467 StLock
<Mutex
> _(managerLock
);
468 timers
.schedule(timer
, when
);
471 void MachServer::clearTimer(Timer
*timer
)
473 StLock
<Mutex
> _(managerLock
);
474 if (timer
->scheduled())
475 timers
.unschedule(timer
);
480 // Notification hooks and shims. Defaults do nothing.
482 void cdsa_mach_notify_dead_name(mach_port_t
, mach_port_name_t port
)
483 { MachServer::active().notifyDeadName(port
); }
485 void MachServer::notifyDeadName(Port
) { }
487 void cdsa_mach_notify_port_deleted(mach_port_t
, mach_port_name_t port
)
488 { MachServer::active().notifyPortDeleted(port
); }
490 void MachServer::notifyPortDeleted(Port
) { }
492 void cdsa_mach_notify_port_destroyed(mach_port_t
, mach_port_name_t port
)
493 { MachServer::active().notifyPortDestroyed(port
); }
495 void MachServer::notifyPortDestroyed(Port
) { }
497 void cdsa_mach_notify_send_once(mach_port_t port
)
498 { MachServer::active().notifySendOnce(port
); }
500 void MachServer::notifySendOnce(Port
) { }
502 void cdsa_mach_notify_no_senders(mach_port_t port
, mach_port_mscount_t count
)
503 { MachServer::active().notifyNoSenders(port
, count
); }
505 void MachServer::notifyNoSenders(Port
, mach_port_mscount_t
) { }
508 } // end namespace MachPlusPlus
510 } // end namespace Security