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);
79 void MachServer::notifyIfDead(Port port
) const
81 port
.requestNotify(mServerPort
, MACH_NOTIFY_DEAD_NAME
, true);
87 // This call will take control of the current thread and use it to service
88 // incoming requests. The thread will not be released until an error happens.
89 // We may also be creating additional threads to service concurrent requests
91 // @@@ Additional threads are not being reaped at this point.
92 // @@@ Msg-errors in additional threads are not acted upon.
94 void MachServer::run(size_t maxSize
, mach_msg_options_t options
)
96 // establish server-global (thread-shared) parameters
98 mMsgOptions
= options
;
100 // establish the thread pool state
101 // (don't need managerLock since we're the only thread as of yet)
102 idleCount
= workerCount
= 1;
103 nextCheckTime
= Time::now() + workerTimeout
;
104 leastIdleWorkers
= 1;
105 highestWorkerCount
= 1;
107 // run server loop in initial (immortal) thread
108 runServerThread(false);
110 // primary server thread exited somehow (not currently possible)
116 // This is the core of a server thread at work. It takes over the thread until
117 // something makes it exit normally. Then it returns. Errors cause exceptions.
118 // This code is loosely based on mach_msg_server.c, but is drifting away for
119 // various reasons of flexibility and resilience.
121 extern "C" boolean_t
cdsa_notify_server(mach_msg_header_t
*in
, mach_msg_header_t
*out
);
123 void MachServer::runServerThread(bool doTimeout
)
125 // allocate request/reply buffers
126 Message
bufRequest(mMaxSize
);
127 Message
bufReply(mMaxSize
);
129 // all exits from runServerThread are through exceptions or "goto exit"
131 // register as a worker thread
132 debug("machsrv", "%p starting service on port %d", this, int(mServerPort
));
133 perThread().server
= this;
136 // process all pending timers
137 while (processTimer()) ;
139 // check for worker idle timeout
140 { StLock
<Mutex
> _(managerLock
);
141 // record idle thread low-water mark in scan interval
142 if (idleCount
< leastIdleWorkers
)
143 leastIdleWorkers
= idleCount
;
145 // perform self-timeout processing
147 if (workerCount
> maxWorkerCount
) {
148 debug("machsrv", "%p too many threads; reaping immediately", this);
151 Time::Absolute rightNow
= Time::now();
152 if (rightNow
>= nextCheckTime
) { // reaping period complete; process
153 uint32 idlers
= leastIdleWorkers
;
154 debug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total",
155 this, idlers
, workerCount
);
156 nextCheckTime
= rightNow
+ workerTimeout
;
157 leastIdleWorkers
= INT_MAX
;
164 // release deferred-release memory
165 releaseDeferredAllocations();
167 // determine next timeout, or zero for infinity
168 bool indefinite
= false;
169 Time::Interval timeout
;
170 { StLock
<Mutex
> _(managerLock
);
171 if (timers
.empty()) {
173 timeout
= workerTimeout
;
178 ? min(workerTimeout
, timers
.next() - Time::now())
179 : timers
.next() - Time::now();
183 // receive next IPC request (or wait for timeout)
184 switch (mach_msg_return_t mr
= indefinite
?
185 mach_msg_overwrite_trap(bufRequest
,
186 MACH_RCV_MSG
| mMsgOptions
,
187 0, mMaxSize
, mPortSet
,
188 MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
,
189 (mach_msg_header_t
*) 0, 0)
191 mach_msg_overwrite_trap(bufRequest
,
192 MACH_RCV_MSG
| MACH_RCV_TIMEOUT
| mMsgOptions
,
193 0, mMaxSize
, mPortSet
,
194 mach_msg_timeout_t(timeout
.mSeconds()), MACH_PORT_NULL
,
195 (mach_msg_header_t
*) 0, 0)) {
196 case MACH_MSG_SUCCESS
:
197 // process received request message below
199 case MACH_RCV_TIMED_OUT
:
200 // back to top for time-related processing
202 case MACH_RCV_TOO_LARGE
:
203 // the kernel destroyed the request
205 case MACH_RCV_INTERRUPTED
:
206 // receive interrupted, try again
212 // process received message
213 if (bufRequest
.msgId() >= MACH_NOTIFY_FIRST
&&
214 bufRequest
.msgId() <= MACH_NOTIFY_LAST
) {
215 // mach kernel notification message
216 // we assume this is quick, so no thread arbitration here
217 cdsa_notify_server(bufRequest
, bufReply
);
219 // normal request message
220 { StLock
<Mutex
> _(managerLock
); idleCount
--; }
222 "servicing port %d request id=%d",
223 bufRequest
.localPort().port(), bufRequest
.msgId());
224 if (bufRequest
.localPort() == mServerPort
) { // primary
225 handle(bufRequest
, bufReply
);
227 for (HandlerSet::const_iterator it
= mHandlers
.begin();
228 it
!= mHandlers
.end(); it
++)
229 if (bufRequest
.localPort() == (*it
)->port())
230 (*it
)->handle(bufRequest
, bufReply
);
232 debug("machsrvreq", "request complete");
233 { StLock
<Mutex
> _(managerLock
); idleCount
++; }
236 // process reply generated by handler
237 if (!(bufReply
.bits() & MACH_MSGH_BITS_COMPLEX
) &&
238 bufReply
.returnCode() != KERN_SUCCESS
) {
239 if (bufReply
.returnCode() == MIG_NO_REPLY
)
241 // don't destroy the reply port right, so we can send an error message
242 bufRequest
.remotePort(MACH_PORT_NULL
);
243 mach_msg_destroy(bufRequest
);
246 if (bufReply
.remotePort() == MACH_PORT_NULL
) {
247 // no reply port, so destroy the reply
248 if (bufReply
.bits() & MACH_MSGH_BITS_COMPLEX
)
254 * We don't want to block indefinitely because the client
255 * isn't receiving messages from the reply port.
256 * If we have a send-once right for the reply port, then
257 * this isn't a concern because the send won't block.
258 * If we have a send right, we need to use MACH_SEND_TIMEOUT.
259 * To avoid falling off the kernel's fast RPC path unnecessarily,
260 * we only supply MACH_SEND_TIMEOUT when absolutely necessary.
262 switch (mach_msg_return_t mr
= mach_msg_overwrite_trap(bufReply
,
263 (MACH_MSGH_BITS_REMOTE(bufReply
.bits()) ==
264 MACH_MSG_TYPE_MOVE_SEND_ONCE
) ?
265 MACH_SEND_MSG
| mMsgOptions
:
266 MACH_SEND_MSG
| MACH_SEND_TIMEOUT
| mMsgOptions
,
267 bufReply
.length(), 0, MACH_PORT_NULL
,
268 0, MACH_PORT_NULL
, NULL
, 0)) {
269 case MACH_MSG_SUCCESS
:
271 case MACH_SEND_INVALID_DEST
:
272 case MACH_SEND_TIMED_OUT
:
273 /* the reply can't be delivered, so destroy it */
274 mach_msg_destroy(bufRequest
);
280 perThread().server
= NULL
;
281 debug("machsrv", "%p ending service on port %d", this, int(mServerPort
));
284 perThread().server
= NULL
;
285 debug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort
));
292 // Manage subsidiary ports
294 void MachServer::add(Handler
&handler
)
296 assert(mHandlers
.find(&handler
) == mHandlers
.end());
297 assert(handler
.port() != MACH_PORT_NULL
);
298 mHandlers
.insert(&handler
);
299 mPortSet
+= handler
.port();
302 void MachServer::remove(Handler
&handler
)
304 assert(mHandlers
.find(&handler
) != mHandlers
.end());
305 mHandlers
.erase(&handler
);
306 mPortSet
-= handler
.port();
311 // Implement a Handler that sends no reply
313 boolean_t
MachServer::NoReplyHandler::handle(mach_msg_header_t
*in
, mach_msg_header_t
*out
)
315 // set up reply message to be valid (enough) and read "do not send reply"
317 out
->msgh_remote_port
= MACH_PORT_NULL
;
318 out
->msgh_size
= sizeof(mig_reply_error_t
);
319 ((mig_reply_error_t
*)out
)->RetCode
= MIG_NO_REPLY
;
321 // call input-only handler
327 // Register a memory block for deferred release.
329 void MachServer::releaseWhenDone(CssmAllocator
&alloc
, void *memory
)
332 set
<Allocation
> &releaseSet
= perThread().deferredAllocations
;
333 assert(releaseSet
.find(Allocation(memory
, alloc
)) == releaseSet
.end());
334 debug("machsrvmem", "%p register %p for release with %p",
335 this, memory
, &alloc
);
336 releaseSet
.insert(Allocation(memory
, alloc
));
342 // Run through the accumulated deferred allocations and release them.
343 // This is done automatically on every pass through the server loop;
344 // it must be called by subclasses that implement their loop in some
346 // @@@X Needs to be thread local
348 void MachServer::releaseDeferredAllocations()
350 set
<Allocation
> &releaseSet
= perThread().deferredAllocations
;
351 for (set
<Allocation
>::iterator it
= releaseSet
.begin(); it
!= releaseSet
.end(); it
++) {
352 debug("machsrvmem", "%p release %p with %p", this, it
->addr
, it
->allocator
);
353 it
->allocator
->free(it
->addr
);
355 releaseSet
.erase(releaseSet
.begin(), releaseSet
.end());
360 // The handler function calls this if it realizes that it might be blocked
361 // (or doing something that takes a long time). We respond by ensuring that
362 // at least one more thread is ready to serve requests.
364 void MachServer::longTermActivity()
366 StLock
<Mutex
> _(managerLock
);
367 if (idleCount
== 0 && workerCount
< maxWorkerCount
) {
368 // spawn a new thread of activity that shares in the server main loop
369 (new LoadThread(*this))->run();
373 void MachServer::LoadThread::action()
375 //@@@ race condition?! can server exit before helpers thread gets here?
377 // register the worker thread and go
378 server
.addThread(this);
380 server
.runServerThread(true);
382 // fell out of server loop by error. Let the thread go quietly
384 server
.removeThread(this);
387 void MachServer::addThread(Thread
*thread
)
389 StLock
<Mutex
> _(managerLock
);
392 debug("machsrv", "%p adding worker thread (%ld workers, %ld idle)",
393 this, workerCount
, idleCount
);
394 workers
.insert(thread
);
397 void MachServer::removeThread(Thread
*thread
)
399 StLock
<Mutex
> _(managerLock
);
402 debug("machsrv", "%p removing worker thread (%ld workers, %ld idle)",
403 this, workerCount
, idleCount
);
404 workers
.erase(thread
);
411 bool MachServer::processTimer()
414 { StLock
<Mutex
> _(managerLock
); // could have multiple threads trying this
415 if (!(top
= static_cast<Timer
*>(timers
.pop(Time::now()))))
416 return false; // nothing (more) to be done now
417 } // drop lock; work has been retrieved
418 debug("machsrvtime", "%p timer %p executing at %.3f",
419 this, top
, Time::now().internalForm());
422 debug("machsrvtime", "%p timer %p done", this, top
);
424 debug("machsrvtime", "%p server timer %p failed with exception", this, top
);
429 void MachServer::setTimer(Timer
*timer
, Time::Absolute when
)
431 StLock
<Mutex
> _(managerLock
);
432 timers
.schedule(timer
, when
);
435 void MachServer::clearTimer(Timer
*timer
)
437 StLock
<Mutex
> _(managerLock
);
438 if (timer
->scheduled())
439 timers
.unschedule(timer
);
444 // Notification hooks and shims. Defaults do nothing.
446 void cdsa_mach_notify_dead_name(mach_port_t
, mach_port_name_t port
)
447 { MachServer::active().notifyDeadName(port
); }
449 void MachServer::notifyDeadName(Port
) { }
451 void cdsa_mach_notify_port_deleted(mach_port_t
, mach_port_name_t port
)
452 { MachServer::active().notifyPortDeleted(port
); }
454 void MachServer::notifyPortDeleted(Port
) { }
456 void cdsa_mach_notify_port_destroyed(mach_port_t
, mach_port_name_t port
)
457 { MachServer::active().notifyPortDestroyed(port
); }
459 void MachServer::notifyPortDestroyed(Port
) { }
461 void cdsa_mach_notify_send_once(mach_port_t
)
462 { MachServer::active().notifySendOnce(); }
464 void MachServer::notifySendOnce() { }
466 void cdsa_mach_notify_no_senders(mach_port_t
)
467 { /* legacy handler - not used by system */ }
470 } // end namespace MachPlusPlus
472 } // end namespace Security