xref: /haiku/src/kits/app/Looper.cpp (revision fef6144999c2fa611f59ee6ffe6dd7999501385c)
1 /*
2  * Copyright 2001-2005, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Erik Jaesler (erik@cgsoftware.com)
7  *		DarkWyrm (bpmagic@columbus.rr.com)
8  *		Ingo Weinhold, bonefish@@users.sf.net
9  *		Axel Dörfler, axeld@pinc-software.de
10  */
11 
12 /**	BLooper class spawns a thread that runs a message loop. */
13 
14 /**
15 	@note	Although I'm implementing "by the book" for now, I would like to
16 			refactor sLooperList and all of the functions that operate on it
17 			into their own class in the BPrivate namespace.
18 
19 			Also considering adding the thread priority when archiving.
20  */
21 
22 // debugging
23 //#define DBG(x) x
24 #define DBG(x)	;
25 #define PRINT(x)	DBG({ printf("[%6ld] ", find_thread(NULL)); printf x; })
26 
27 /*
28 #include <Autolock.h>
29 #include <Locker.h>
30 static BLocker sDebugPrintLocker("BLooper debug print");
31 #define PRINT(x)	DBG({						\
32 	BAutolock _(sDebugPrintLocker);				\
33 	debug_printf("[%6ld] ", find_thread(NULL));	\
34 	debug_printf x;								\
35 })
36 */
37 
38 #include <stdio.h>
39 
40 #include <Autolock.h>
41 #include <Looper.h>
42 #include <Message.h>
43 #include <MessageFilter.h>
44 #include <MessageQueue.h>
45 #include <Messenger.h>
46 #include <PropertyInfo.h>
47 
48 #include <LooperList.h>
49 #include <ObjectLocker.h>
50 #include <TokenSpace.h>
51 
52 
53 #define FILTER_LIST_BLOCK_SIZE	5
54 #define DATA_BLOCK_SIZE			5
55 
56 // Globals ---------------------------------------------------------------------
57 using BPrivate::gDefaultTokens;
58 using BPrivate::gLooperList;
59 using BPrivate::BObjectLocker;
60 using BPrivate::BLooperList;
61 
62 port_id _get_looper_port_(const BLooper* looper);
63 bool _use_preferred_target_(BMessage* msg) { return msg->fPreferred; }
64 int32 _get_message_target_(BMessage* msg) { return msg->fTarget; }
65 
66 uint32 BLooper::sLooperID = (uint32)B_ERROR;
67 team_id BLooper::sTeamID = (team_id)B_ERROR;
68 
69 enum {
70 	BLOOPER_PROCESS_INTERNALLY = 0,
71 	BLOOPER_HANDLER_BY_INDEX
72 };
73 
74 static property_info gLooperPropInfo[] = {
75 	{
76 		"Handler",
77 			{},
78 			{B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
79 			NULL, BLOOPER_HANDLER_BY_INDEX,
80 			{},
81 			{},
82 			{}
83 	},
84 	{
85 		"Handlers",
86 			{B_GET_PROPERTY},
87 			{B_DIRECT_SPECIFIER},
88 			NULL, BLOOPER_PROCESS_INTERNALLY,
89 			{B_MESSENGER_TYPE},
90 			{},
91 			{}
92 	},
93 	{
94 		"Handler",
95 			{B_COUNT_PROPERTIES},
96 			{B_DIRECT_SPECIFIER},
97 			NULL, BLOOPER_PROCESS_INTERNALLY,
98 			{B_INT32_TYPE},
99 			{},
100 			{}
101 	},
102 	{}
103 };
104 
105 struct _loop_data_ {
106 	BLooper*	looper;
107 	thread_id	thread;
108 };
109 
110 
111 //	#pragma mark -
112 
113 
114 BLooper::BLooper(const char* name, int32 priority, int32 port_capacity)
115 	:	BHandler(name)
116 {
117 	InitData(name, priority, port_capacity);
118 }
119 
120 
121 BLooper::~BLooper()
122 {
123 	if (fRunCalled && !fTerminating) {
124 		debugger("You can't call delete on a BLooper object "
125 				 "once it is running.");
126 	}
127 
128 	Lock();
129 
130 	// In case the looper thread calls Quit() fLastMessage is not deleted.
131 	if (fLastMessage) {
132 		delete fLastMessage;
133 		fLastMessage = NULL;
134 	}
135 
136 	// Close the message port and read and reply to the remaining messages.
137 	if (fMsgPort > 0)
138 		close_port(fMsgPort);
139 
140 	BMessage *msg;
141 	// Clear the queue so our call to IsMessageWaiting() below doesn't give
142 	// us bogus info
143 	while ((msg = fQueue->NextMessage()) != NULL) {
144 		delete msg;			// msg will automagically post generic reply
145 	}
146 
147 	do {
148 		delete ReadMessageFromPort(0);
149 			// msg will automagically post generic reply
150 	} while (IsMessageWaiting());
151 
152 	delete fQueue;
153 	delete_port(fMsgPort);
154 
155 	// Clean up our filters
156 	SetCommonFilterList(NULL);
157 
158 	BObjectLocker<BLooperList> ListLock(gLooperList);
159 	RemoveHandler(this);
160 
161 	// Remove all the "child" handlers
162 	BHandler *child;
163 	while (CountHandlers()) {
164 		child = HandlerAt(0);
165 		if (child)
166 			RemoveHandler(child);
167 	}
168 
169 	Unlock();
170 	RemoveLooper(this);
171 	delete_sem(fLockSem);
172 }
173 
174 
175 BLooper::BLooper(BMessage *data)
176 	: BHandler(data)
177 {
178 	int32 portCapacity;
179 	if (data->FindInt32("_port_cap", &portCapacity) != B_OK
180 		|| portCapacity < 0)
181 		portCapacity = B_LOOPER_PORT_DEFAULT_CAPACITY;
182 
183 	InitData(Name(), B_NORMAL_PRIORITY, portCapacity);
184 }
185 
186 
187 BArchivable *
188 BLooper::Instantiate(BMessage *data)
189 {
190 	if (validate_instantiation(data, "BLooper"))
191 		return new BLooper(data);
192 
193 	return NULL;
194 }
195 
196 
197 status_t
198 BLooper::Archive(BMessage *data, bool deep) const
199 {
200 	status_t status = BHandler::Archive(data, deep);
201 	if (status < B_OK)
202 		return status;
203 
204 	port_info info;
205 	status = get_port_info(fMsgPort, &info);
206 	if (status == B_OK)
207 		status = data->AddInt32("_port_cap", info.capacity);
208 
209 	return status;
210 }
211 
212 
213 status_t
214 BLooper::PostMessage(uint32 command)
215 {
216 	BMessage message(command);
217 	return _PostMessage(&message, this, NULL);
218 }
219 
220 
221 status_t
222 BLooper::PostMessage(BMessage *message)
223 {
224 	return _PostMessage(message, this, NULL);
225 }
226 
227 
228 status_t
229 BLooper::PostMessage(uint32 command, BHandler *handler,
230 	BHandler *replyTo)
231 {
232 	BMessage message(command);
233 	return _PostMessage(&message, handler, replyTo);
234 }
235 
236 
237 status_t
238 BLooper::PostMessage(BMessage *message, BHandler *handler,
239 	BHandler *replyTo)
240 {
241 	return _PostMessage(message, handler, replyTo);
242 }
243 
244 
245 void
246 BLooper::DispatchMessage(BMessage *message, BHandler *handler)
247 {
248 	PRINT(("BLooper::DispatchMessage(%.4s)\n", (char*)&message->what));
249 	/** @note
250 		Initially, DispatchMessage() was locking the looper, calling the
251 		filtering API, determining whether to use fPreferred or not, and
252 		deleting the message.  A look at the BeBook, however, reveals that
253 		all this function does is handle its own B_QUIT_REQUESTED messages
254 		and pass everything else to handler->MessageReceived().  Clearly the
255 		rest must be happening in task_looper().  This makes a lot of sense
256 		because otherwise every derived class would have to figure out when
257 		to use fPreferred, handle the locking and filtering and delete the
258 		message.  Even if the BeBook didn't say as much, it would make total
259 		sense to hoist that functionality out of here and into task_looper().
260 	*/
261 	switch (message->what) {
262 		case _QUIT_:
263 			// Can't call Quit() to do this, because of the slight chance
264 			// another thread with have us locked between now and then.
265 			fTerminating = true;
266 
267 			// After returning from DispatchMessage(), the looper will be
268 			// deleted in _task0_()
269 			break;
270 
271 		case B_QUIT_REQUESTED:
272 			if (handler == this) {
273 				do_quit_requested(message);
274 				break;
275 			}
276 
277 			// fall through
278 
279 		default:
280 			handler->MessageReceived(message);
281 			break;
282 	}
283 	PRINT(("BLooper::DispatchMessage() done\n"));
284 }
285 
286 
287 void
288 BLooper::MessageReceived(BMessage *msg)
289 {
290 	// TODO: verify
291 	// The BeBook says this "simply calls the inherited function. ...the BLooper
292 	// implementation does nothing of importance."  Which is not the same as
293 	// saying it does nothing.  Investigate.
294 	BHandler::MessageReceived(msg);
295 }
296 
297 
298 BMessage*
299 BLooper::CurrentMessage() const
300 {
301 	return fLastMessage;
302 }
303 
304 
305 BMessage*
306 BLooper::DetachCurrentMessage()
307 {
308 	Lock();
309 	BMessage* msg = fLastMessage;
310 	fLastMessage = NULL;
311 	fQueue->RemoveMessage(msg);
312 	Unlock();
313 	return msg;
314 }
315 
316 
317 BMessageQueue*
318 BLooper::MessageQueue() const
319 {
320 	return fQueue;
321 }
322 
323 
324 bool
325 BLooper::IsMessageWaiting() const
326 {
327 	AssertLocked();
328 
329 	if (!fQueue->IsEmpty())
330 		return true;
331 
332 /**
333 	@note:	What we're doing here differs slightly from the R5 implementation.
334 			It appears that they probably return count != 0, which gives an
335 			incorrect true result when port_buffer_size_etc() would block --
336 			which indicates that the port's buffer is empty, so we should return
337 			false.  Since we don't actually care about what the error is, we
338 			just return count > 0.  This has some interesting consequences in
339 			that we will correctly return 'false' if the port is empty
340 			(B_WOULD_BLOCK), whereas R5 will return true.  We call that a bug
341 			where I come from. ;)
342  */
343 	int32 count;
344 	do {
345 		count = port_buffer_size_etc(fMsgPort, B_RELATIVE_TIMEOUT, 0);
346 	} while (count == B_INTERRUPTED);
347 
348 	return count > 0;
349 }
350 
351 
352 void
353 BLooper::AddHandler(BHandler* handler)
354 {
355 	if (handler == NULL)
356 		return;
357 
358 	AssertLocked();
359 
360 	if (handler->Looper() == NULL) {
361 		fHandlers.AddItem(handler);
362 		handler->SetLooper(this);
363 		if (handler != this)	// avoid a cycle
364 			handler->SetNextHandler(this);
365 	}
366 }
367 
368 
369 bool
370 BLooper::RemoveHandler(BHandler* handler)
371 {
372 	if (handler == NULL)
373 		return false;
374 
375 	AssertLocked();
376 
377 	if (handler->Looper() == this && fHandlers.RemoveItem(handler)) {
378 		if (handler == fPreferred)
379 			fPreferred = NULL;
380 
381 		handler->SetNextHandler(NULL);
382 		handler->SetLooper(NULL);
383 		return true;
384 	}
385 
386 	return false;
387 }
388 
389 
390 int32
391 BLooper::CountHandlers() const
392 {
393 	AssertLocked();
394 
395 	return fHandlers.CountItems();
396 }
397 
398 
399 BHandler*
400 BLooper::HandlerAt(int32 index) const
401 {
402 	AssertLocked();
403 
404 	return (BHandler*)fHandlers.ItemAt(index);
405 }
406 
407 
408 int32
409 BLooper::IndexOf(BHandler* handler) const
410 {
411 	AssertLocked();
412 
413 	return fHandlers.IndexOf(handler);
414 }
415 
416 
417 BHandler*
418 BLooper::PreferredHandler() const
419 {
420 	return fPreferred;
421 }
422 
423 
424 void
425 BLooper::SetPreferredHandler(BHandler* handler)
426 {
427 	if (handler && handler->Looper() == this && IndexOf(handler) >= 0) {
428 		fPreferred = handler;
429 	} else {
430 		fPreferred = NULL;
431 	}
432 }
433 
434 
435 thread_id
436 BLooper::Run()
437 {
438 	AssertLocked();
439 
440 	if (fRunCalled) {
441 		// Not allowed to call Run() more than once
442 		debugger("can't call BLooper::Run twice!");
443 		return fTaskID;
444 	}
445 
446 	fTaskID = spawn_thread(_task0_, Name(), fInitPriority, this);
447 	if (fTaskID < B_OK)
448 		return fTaskID;
449 
450 	if (fMsgPort < B_OK)
451 		return fMsgPort;
452 
453 	fRunCalled = true;
454 	Unlock();
455 
456 	status_t err = resume_thread(fTaskID);
457 	if (err < B_OK)
458 		return err;
459 
460 	return fTaskID;
461 }
462 
463 
464 void
465 BLooper::Quit()
466 {
467 	PRINT(("BLooper::Quit()\n"));
468 
469 	if (!IsLocked()) {
470 		printf("ERROR - you must Lock a looper before calling Quit(), "
471 			   "team=%ld, looper=%s\n", Team(), Name() ? Name() : "unnamed");
472 	}
473 
474 	// Try to lock
475 	if (!Lock()) {
476 		// We're toast already
477 		return;
478 	}
479 
480 	PRINT(("  is locked\n"));
481 
482 	if (!fRunCalled) {
483 		PRINT(("  Run() has not been called yet\n"));
484 		fTerminating = true;
485 		delete this;
486 	} else if (find_thread(NULL) == fTaskID) {
487 		PRINT(("  We are the looper thread\n"));
488 		fTerminating = true;
489 		delete this;
490 		exit_thread(0);
491 	} else {
492 		PRINT(("  Run() has already been called and we are not the looper thread\n"));
493 
494 		// As with sem in _Lock(), we need to cache this here in case the looper
495 		// disappears before we get to the wait_for_thread() below
496 		thread_id tid = Thread();
497 
498 		// We need to unlock here. Otherwise the looper thread can't
499 		// dispatch the _QUIT_ message we're going to post.
500 		UnlockFully();
501 
502 		// As per the BeBook, if we've been called by a thread other than
503 		// our own, the rest of the message queue has to get processed.  So
504 		// we put this in the queue, and when it shows up, we'll call Quit()
505 		// from our own thread.
506 		// A little testing with BMessageFilter shows _QUIT_ is being used here.
507 		// I got suspicious when my test QuitRequested() wasn't getting called
508 		// when Quit() was invoked from another thread.  Makes a nice proof that
509 		// this is how it's handled, too.
510 
511 		while (PostMessage(_QUIT_) == B_WOULD_BLOCK) {
512 			// There's a slight chance that PostMessage() will return B_WOULD_BLOCK
513 			// because the port is full, so we'll wait a bit and re-post until
514 			// we won't block.
515 			snooze(25000);
516 		}
517 
518 		// We have to wait until the looper is done processing any remaining messages.
519 		int32 temp;
520 		while (wait_for_thread(tid, &temp) == B_INTERRUPTED)
521 			;
522 	}
523 
524 	PRINT(("BLooper::Quit() done\n"));
525 }
526 
527 
528 bool
529 BLooper::QuitRequested()
530 {
531 	return true;
532 }
533 
534 
535 bool
536 BLooper::Lock()
537 {
538 	// Defer to global _Lock(); see notes there
539 	return _Lock(this, -1, B_INFINITE_TIMEOUT) == B_OK;
540 }
541 
542 
543 void
544 BLooper::Unlock()
545 {
546 PRINT(("BLooper::Unlock()\n"));
547 	//	Make sure we're locked to begin with
548 	AssertLocked();
549 
550 	//	Decrement fOwnerCount
551 	--fOwnerCount;
552 PRINT(("  fOwnerCount now: %ld\n", fOwnerCount));
553 	//	Check to see if the owner still wants a lock
554 	if (fOwnerCount == 0) {
555 		//	Set fOwner to invalid thread_id (< 0)
556 		fOwner = -1;
557 
558 		//	Decrement requested lock count (using fAtomicCount for this)
559 /*		int32 atomicCount =*/ atomic_add(&fAtomicCount, -1);
560 PRINT(("  fAtomicCount now: %ld\n", fAtomicCount));
561 
562 		//	Check if anyone is waiting for a lock
563 // bonefish: Currently _Lock() always acquires the semaphore.
564 //		if (atomicCount > 0)
565 		{
566 			//	release the lock
567 			release_sem(fLockSem);
568 		}
569 	}
570 PRINT(("BLooper::Unlock() done\n"));
571 }
572 
573 
574 bool
575 BLooper::IsLocked() const
576 {
577 	// We have to lock the list for the call to IsLooperValid().  Has the side
578 	// effect of not letting the looper get deleted while we're here.
579 	BObjectLocker<BLooperList> ListLock(gLooperList);
580 
581 	if (!ListLock.IsLocked()) {
582 		// If we can't lock the list, our semaphore is probably toast
583 		return false;
584 	}
585 
586 	if (!IsLooperValid(this)) {
587 		// The looper is gone, so of course it's not locked
588 		return false;
589 	}
590 
591 	// Got this from Jeremy's BLocker implementation
592 	return find_thread(NULL) == fOwner;
593 }
594 
595 
596 status_t
597 BLooper::LockWithTimeout(bigtime_t timeout)
598 {
599 	return _Lock(this, -1, timeout);
600 }
601 
602 
603 thread_id
604 BLooper::Thread() const
605 {
606 	return fTaskID;
607 }
608 
609 
610 team_id
611 BLooper::Team() const
612 {
613 	return sTeamID;
614 }
615 
616 
617 BLooper*
618 BLooper::LooperForThread(thread_id tid)
619 {
620 	BObjectLocker<BLooperList> ListLock(gLooperList);
621 	if (ListLock.IsLocked())
622 		return gLooperList.LooperForThread(tid);
623 
624 	return NULL;
625 }
626 
627 
628 thread_id
629 BLooper::LockingThread() const
630 {
631 	return fOwner;
632 }
633 
634 
635 int32
636 BLooper::CountLocks() const
637 {
638 	return fOwnerCount;
639 }
640 
641 
642 int32
643 BLooper::CountLockRequests() const
644 {
645 	return fAtomicCount;
646 }
647 
648 
649 sem_id
650 BLooper::Sem() const
651 {
652 	return fLockSem;
653 }
654 
655 
656 BHandler*
657 BLooper::ResolveSpecifier(BMessage* msg, int32 index,
658 	BMessage* specifier, int32 form, const char* property)
659 {
660 /**
661 	@note	When I was first dumping the results of GetSupportedSuites() from
662 			various classes, the use of the extra_data field was quite
663 			mysterious to me.  Then I dumped BApplication and compared the
664 			result against the BeBook's docs for scripting BApplication.  A
665 			bunch of it isn't documented, but what is tipped me to the idea
666 			that the extra_data is being used as a quick and dirty way to tell
667 			what scripting "command" has been sent, e.g., for easy use in a
668 			switch statement.  Would certainly be a lot faster than a bunch of
669 			string comparisons -- which wouldn't tell the whole story anyway,
670 			because of the same name being used for multiple properties.
671  */
672 	BPropertyInfo PropertyInfo(gLooperPropInfo);
673 	uint32 data;
674 	status_t err = B_OK;
675 	const char* errMsg = "";
676 	if (PropertyInfo.FindMatch(msg, index, specifier, form, property, &data) >= 0) {
677 		switch (data) {
678 			case BLOOPER_PROCESS_INTERNALLY:
679 				return this;
680 
681 			case BLOOPER_HANDLER_BY_INDEX:
682 			{
683 				int32 index = specifier->FindInt32("index");
684 				if (form == B_REVERSE_INDEX_SPECIFIER) {
685 					index = CountHandlers() - index;
686 				}
687 				BHandler* target = HandlerAt(index);
688 				if (target) {
689 					// Specifier has been fully handled
690 					msg->PopSpecifier();
691 					return target;
692 				} else {
693 					err = B_BAD_INDEX;
694 					errMsg = "handler index out of range";
695 				}
696 				break;
697 			}
698 
699 			default:
700 				err = B_BAD_SCRIPT_SYNTAX;
701 				errMsg = "Didn't understand the specifier(s)";
702 				break;
703 		}
704 	} else {
705 		return BHandler::ResolveSpecifier(msg, index, specifier, form,
706 			property);
707 	}
708 
709 	BMessage Reply(B_MESSAGE_NOT_UNDERSTOOD);
710 	Reply.AddInt32("error", err);
711 	Reply.AddString("message", errMsg);
712 	msg->SendReply(&Reply);
713 
714 	return NULL;
715 }
716 
717 
718 status_t
719 BLooper::GetSupportedSuites(BMessage* data)
720 {
721 	if (data == NULL)
722 		return B_BAD_VALUE;
723 
724 	status_t status = data->AddString("Suites", "suite/vnd.Be-handler");
725 	if (status == B_OK) {
726 		BPropertyInfo PropertyInfo(gLooperPropInfo);
727 		status = data->AddFlat("message", &PropertyInfo);
728 		if (status == B_OK)
729 			status = BHandler::GetSupportedSuites(data);
730 	}
731 
732 	return status;
733 }
734 
735 
736 void
737 BLooper::AddCommonFilter(BMessageFilter* filter)
738 {
739 	if (!filter)
740 		return;
741 
742 	AssertLocked();
743 
744 	if (filter->Looper()) {
745 		debugger("A MessageFilter can only be used once.");
746 		return;
747 	}
748 
749 	if (!fCommonFilters)
750 		fCommonFilters = new BList(FILTER_LIST_BLOCK_SIZE);
751 
752 	filter->SetLooper(this);
753 	fCommonFilters->AddItem(filter);
754 }
755 
756 
757 bool
758 BLooper::RemoveCommonFilter(BMessageFilter* filter)
759 {
760 	AssertLocked();
761 
762 	if (!fCommonFilters)
763 		return false;
764 
765 	bool result = fCommonFilters->RemoveItem(filter);
766 	if (result)
767 		filter->SetLooper(NULL);
768 
769 	return result;
770 }
771 
772 
773 void
774 BLooper::SetCommonFilterList(BList* filters)
775 {
776 	// We have a somewhat serious problem here.  It is entirely possible in R5
777 	// to assign a given list of filters to *two* BLoopers simultaneously.  This
778 	// becomes problematic when the loopers are destroyed: the last looper
779 	// destroyed will have a problem when it tries to delete a filter list that
780 	// has already been deleted.  In R5, this results in a general protection
781 	// fault.  We fix this by checking the filter list for ownership issues.
782 
783 	AssertLocked();
784 
785 	BMessageFilter* filter;
786 	if (filters) {
787 		// Check for ownership issues
788 		for (int32 i = 0; i < filters->CountItems(); ++i) {
789 			filter = (BMessageFilter*)filters->ItemAt(i);
790 			if (filter->Looper()) {
791 				debugger("A MessageFilter can only be used once.");
792 				return;
793 			}
794 		}
795 	}
796 
797 	if (fCommonFilters) {
798 		for (int32 i = 0; i < fCommonFilters->CountItems(); ++i) {
799 			delete (BMessageFilter*)fCommonFilters->ItemAt(i);
800 		}
801 
802 		delete fCommonFilters;
803 		fCommonFilters = NULL;
804 	}
805 
806 	// Per the BeBook, we take ownership of the list
807 	fCommonFilters = filters;
808 	if (fCommonFilters) {
809 		for (int32 i = 0; i < fCommonFilters->CountItems(); ++i) {
810 			filter = (BMessageFilter*)fCommonFilters->ItemAt(i);
811 			filter->SetLooper(this);
812 		}
813 	}
814 }
815 
816 
817 BList*
818 BLooper::CommonFilterList() const
819 {
820 	return fCommonFilters;
821 }
822 
823 
824 status_t
825 BLooper::Perform(perform_code d, void* arg)
826 {
827 	// This is sort of what we're doing for this function everywhere
828 	return BHandler::Perform(d, arg);
829 }
830 
831 
832 BMessage*
833 BLooper::MessageFromPort(bigtime_t timeout)
834 {
835 	return ReadMessageFromPort(timeout);
836 }
837 
838 
839 void BLooper::_ReservedLooper1() {}
840 void BLooper::_ReservedLooper2() {}
841 void BLooper::_ReservedLooper3() {}
842 void BLooper::_ReservedLooper4() {}
843 void BLooper::_ReservedLooper5() {}
844 void BLooper::_ReservedLooper6() {}
845 
846 
847 BLooper::BLooper(const BLooper&)
848 {
849 	// Copy construction not allowed
850 }
851 
852 
853 BLooper& BLooper::operator=(const BLooper& )
854 {
855 	// Looper copying not allowed
856 	return *this;
857 }
858 
859 
860 BLooper::BLooper(int32 priority, port_id port, const char* name)
861 {
862 	// This must be a legacy constructor
863 	fMsgPort = port;
864 	InitData(name, priority, B_LOOPER_PORT_DEFAULT_CAPACITY);
865 }
866 
867 
868 status_t
869 BLooper::_PostMessage(BMessage *msg, BHandler *handler,
870 	BHandler *replyTo)
871 {
872 	BObjectLocker<BLooperList> listLocker(gLooperList);
873 	if (!listLocker.IsLocked())
874 		return B_ERROR;
875 
876 	if (!IsLooperValid(this))
877 		return B_BAD_VALUE;
878 
879 	// Does handler belong to this looper?
880 	if (handler && handler->Looper() != this)
881 		return B_MISMATCHED_VALUES;
882 
883 	status_t status;
884 	BMessenger messenger(handler, this, &status);
885 	if (status == B_OK)
886 		status = messenger.SendMessage(msg, replyTo, 0);
887 
888 	return status;
889 }
890 
891 
892 status_t
893 BLooper::_Lock(BLooper* loop, port_id port, bigtime_t timeout)
894 {
895 PRINT(("BLooper::_Lock(%p, %lx)\n", loop, port));
896 /**
897 	@note	The assumption I'm under here is that since we can get the port of
898 			the BLooper directly from the BLooper itself, the port parameter is
899 			for identifying BLoopers by port_id when a pointer to the BLooper in
900 			question is not available.  So this function has two modes:
901 				o When loop != NULL, use it directly
902 				o When loop == NULL and port is valid, use the port_id to get
903 				  the looper
904 			I scoured the docs to find out what constitutes a valid port_id to
905 			no avail.  Since create_port uses the standard error values in its
906 			returned port_id, I'll assume that anything less than zero is a safe
907 			bet as an *invalid* port_id.  I'm guessing that, like thread and
908 			semaphore ids, anything >= zero is valid.  So, the short version of
909 			this reads: if you don't want to find by port_id, make port = -1.
910 
911 			Another assumption I'm making is that Lock() and LockWithTimeout()
912 			are covers for this function.  If it turns out that we don't really
913 			need this function, I may refactor this code into LockWithTimeout()
914 			and have Lock() call it instead.  This function could then be
915 			removed.
916  */
917 
918 	//	Check params (loop, port)
919 	if (!loop && port < 0)
920 	{
921 PRINT(("BLooper::_Lock() done 1\n"));
922 		return B_BAD_VALUE;
923 	}
924 
925 	// forward declared so I can use BAutolock on sLooperListLock
926 	thread_id curThread;
927 	sem_id sem;
928 
929 /**
930 	@note	We lock the looper list at the start of the lock operation to
931 			prevent the looper getting removed from the list while we're
932 			doing list operations.  Also ensures that the looper doesn't
933 			get deleted here (since ~BLooper() has to lock the list as
934 			well to remove itself).
935  */
936 	{
937 		BObjectLocker<BLooperList> ListLock(gLooperList);
938 		if (!ListLock.IsLocked())
939 		{
940 			// If we can't lock, the semaphore is probably
941 			// gone, which leaves us in no-man's land
942 PRINT(("BLooper::_Lock() done 2\n"));
943 			return B_BAD_VALUE;
944 		}
945 
946 		//	Look up looper by port_id, if necessary
947 		if (!loop)
948 		{
949 			loop = LooperForPort(port);
950 			if (!loop)
951 			{
952 PRINT(("BLooper::_Lock() done 3\n"));
953 				return B_BAD_VALUE;
954 			}
955 		}
956 		else
957 		{
958 			//	Check looper validity
959 			if (!IsLooperValid(loop))
960 			{
961 PRINT(("BLooper::_Lock() done 4\n"));
962 				return B_BAD_VALUE;
963 			}
964 		}
965 
966 		//	Is the looper trying to lock itself?
967 		//	Check for nested lock attempt
968 		curThread = find_thread(NULL);
969 		if (curThread == loop->fOwner)
970 		{
971 			//	Bump fOwnerCount
972 			++loop->fOwnerCount;
973 PRINT(("BLooper::_Lock() done 5: fOwnerCount: %ld\n", loop->fOwnerCount));
974 			return B_OK;
975 		}
976 
977 		//	Something external to the looper is attempting to lock
978 		//	Cache the semaphore
979 		sem = loop->fLockSem;
980 
981 		//	Validate the semaphore
982 		if (sem < 0)
983 		{
984 PRINT(("BLooper::_Lock() done 6\n"));
985 			return B_BAD_VALUE;
986 		}
987 
988 		//	Bump the requested lock count (using fAtomicCount for this)
989 		atomic_add(&loop->fAtomicCount, 1);
990 
991 		// sLooperListLock automatically released here
992 	}
993 
994 /**
995 	@note	We have to operate with the looper list unlocked during semaphore
996 			acquisition so that the rest of the application doesn't have to
997 			wait for this lock to happen.  This is why we cached fLockSem
998 			earlier -- with the list unlocked, the looper might get deleted
999 			right out from under us.  This is also why we use a raw semaphore
1000 			instead of the easier-to-deal-with BLocker; you can't cache a
1001 			BLocker.
1002  */
1003 	//	acquire the lock
1004 	status_t err;
1005 	do
1006 	{
1007 		err = acquire_sem_etc(sem, 1, B_RELATIVE_TIMEOUT, timeout);
1008 	} while (err == B_INTERRUPTED);
1009 
1010 	if (!err)
1011 	{
1012 		//		Assign current thread to fOwner
1013 		loop->fOwner = curThread;
1014 		//		Reset fOwnerCount to 1
1015 		loop->fOwnerCount = 1;
1016 	}
1017 
1018 	PRINT(("BLooper::_Lock() done: %lx\n", err));
1019 	return err;
1020 }
1021 
1022 
1023 status_t
1024 BLooper::_LockComplete(BLooper *looper, int32 old, thread_id this_tid,
1025 	sem_id sem, bigtime_t timeout)
1026 {
1027 	// What is this for?  Hope I'm not missing something conceptually here ...
1028 	return B_ERROR;
1029 }
1030 
1031 
1032 void
1033 BLooper::InitData()
1034 {
1035 	fOwner = B_ERROR;
1036 	fRunCalled = false;
1037 	fQueue = new BMessageQueue();
1038 	fCommonFilters = NULL;
1039 	fLastMessage = NULL;
1040 	fPreferred = NULL;
1041 	fTaskID = B_ERROR;
1042 	fTerminating = false;
1043 	fMsgPort = -1;
1044 
1045 	if (sTeamID == -1) {
1046 		thread_info info;
1047 		get_thread_info(find_thread(NULL), &info);
1048 		sTeamID = info.team;
1049 	}
1050 }
1051 
1052 
1053 void
1054 BLooper::InitData(const char *name, int32 priority, int32 portCapacity)
1055 {
1056 	InitData();
1057 
1058 	if (name == NULL)
1059 		name = "anonymous looper";
1060 
1061 	fLockSem = create_sem(1, name);
1062 
1063 	if (portCapacity <= 0)
1064 		portCapacity = B_LOOPER_PORT_DEFAULT_CAPACITY;
1065 
1066 	fMsgPort = create_port(portCapacity, name);
1067 
1068 	fInitPriority = priority;
1069 
1070 	BObjectLocker<BLooperList> ListLock(gLooperList);
1071 	AddLooper(this);
1072 	AddHandler(this);
1073 }
1074 
1075 
1076 void
1077 BLooper::AddMessage(BMessage* msg)
1078 {
1079 	_AddMessagePriv(msg);
1080 
1081 	// ToDo: if called from a different thread, we need to wake up the looper
1082 }
1083 
1084 
1085 void
1086 BLooper::_AddMessagePriv(BMessage* msg)
1087 {
1088 	// ToDo: if no target token is specified, set to preferred handler
1089 	// Others may want to peek into our message queue, so the preferred
1090 	// handler must be set correctly already if no token was given
1091 
1092 	fQueue->AddMessage(msg);
1093 }
1094 
1095 
1096 status_t
1097 BLooper::_task0_(void *arg)
1098 {
1099 	BLooper *looper = (BLooper *)arg;
1100 
1101 	PRINT(("LOOPER: _task0_()\n"));
1102 
1103 	if (looper->Lock()) {
1104 		PRINT(("LOOPER: looper locked\n"));
1105 		looper->task_looper();
1106 
1107 		delete looper;
1108 	}
1109 
1110 	PRINT(("LOOPER: _task0_() done: thread %ld\n", find_thread(NULL)));
1111 	return B_OK;
1112 }
1113 
1114 
1115 void *
1116 BLooper::ReadRawFromPort(int32 *msgCode, bigtime_t timeout)
1117 {
1118 	PRINT(("BLooper::ReadRawFromPort()\n"));
1119 	int8 *msgBuffer = NULL;
1120 	ssize_t bufferSize;
1121 
1122 	do {
1123 		bufferSize = port_buffer_size_etc(fMsgPort, B_RELATIVE_TIMEOUT, timeout);
1124 	} while (bufferSize == B_INTERRUPTED);
1125 
1126 	if (bufferSize < B_OK) {
1127 		PRINT(("BLooper::ReadRawFromPort(): failed: %ld\n", bufferSize));
1128 		return NULL;
1129 	}
1130 
1131 	if (bufferSize > 0)
1132 		msgBuffer = new int8[bufferSize];
1133 
1134 	// we don't want to wait again here, since that can only mean
1135 	// that someone else has read our message and our bufferSize
1136 	// is now probably wrong
1137 	PRINT(("read_port()...\n"));
1138 	bufferSize = read_port_etc(fMsgPort, msgCode, msgBuffer, bufferSize,
1139 					  B_RELATIVE_TIMEOUT, 0);
1140 	if (bufferSize < B_OK) {
1141 		delete[] msgBuffer;
1142 		return NULL;
1143 	}
1144 
1145 	PRINT(("BLooper::ReadRawFromPort() read: %.4s, %p\n", (char *)msgCode, msgBuffer));
1146 	return msgBuffer;
1147 }
1148 
1149 
1150 BMessage *
1151 BLooper::ReadMessageFromPort(bigtime_t tout)
1152 {
1153 	PRINT(("BLooper::ReadMessageFromPort()\n"));
1154 	int32 msgcode;
1155 	BMessage* bmsg;
1156 
1157 	void* msgbuffer = ReadRawFromPort(&msgcode, tout);
1158 	if (!msgbuffer)
1159 		return NULL;
1160 
1161 	bmsg = ConvertToMessage(msgbuffer, msgcode);
1162 
1163 	delete[] (int8*)msgbuffer;
1164 
1165 PRINT(("BLooper::ReadMessageFromPort() done: %p\n", bmsg));
1166 	return bmsg;
1167 }
1168 //------------------------------------------------------------------------------
1169 BMessage* BLooper::ConvertToMessage(void* raw, int32 code)
1170 {
1171 PRINT(("BLooper::ConvertToMessage()\n"));
1172 	BMessage* bmsg = new BMessage(code);
1173 
1174 	if (raw != NULL)
1175 	{
1176 		if (bmsg->Unflatten((const char*)raw) != B_OK)
1177 		{
1178 PRINT(("BLooper::ConvertToMessage(): unflattening message failed\n"));
1179 			delete bmsg;
1180 			bmsg = NULL;
1181 		}
1182 	}
1183 
1184 PRINT(("BLooper::ConvertToMessage(): %p\n", bmsg));
1185 	return bmsg;
1186 }
1187 
1188 
1189 void
1190 BLooper::task_looper()
1191 {
1192 	PRINT(("BLooper::task_looper()\n"));
1193 	//	Check that looper is locked (should be)
1194 	AssertLocked();
1195 	//	Unlock the looper
1196 	Unlock();
1197 
1198 	if (IsLocked())
1199 		debugger("looper must not be locked!");
1200 
1201 	//	loop: As long as we are not terminating.
1202 	while (!fTerminating)
1203 	{
1204 		PRINT(("LOOPER: outer loop\n"));
1205 		// TODO: timeout determination algo
1206 		//	Read from message port (how do we determine what the timeout is?)
1207 		PRINT(("LOOPER: MessageFromPort()...\n"));
1208 		BMessage *msg = MessageFromPort();
1209 		PRINT(("LOOPER: ...done\n"));
1210 
1211 		//	Did we get a message?
1212 		if (msg)
1213 			_AddMessagePriv(msg);
1214 
1215 		//	Get message count from port
1216 		int32 msgCount = port_count(fMsgPort);
1217 		for (int32 i = 0; i < msgCount; ++i) {
1218 			//	Read 'count' messages from port (so we will not block)
1219 			//	We use zero as our timeout since we know there is stuff there
1220 			msg = MessageFromPort(0);
1221 			//	Add messages to queue
1222 			if (msg)
1223 				_AddMessagePriv(msg);
1224 		}
1225 
1226 		//	loop: As long as there are messages in the queue and the port is
1227 		//		  empty... and we are not terminating, of course.
1228 		bool dispatchNextMessage = true;
1229 		while (!fTerminating && dispatchNextMessage) {
1230 			PRINT(("LOOPER: inner loop\n"));
1231 			//	Get next message from queue (assign to fLastMessage)
1232 			fLastMessage = fQueue->NextMessage();
1233 
1234 			//	Lock the looper
1235 			Lock();
1236 			if (!fLastMessage) {
1237 				// No more messages: Unlock the looper and terminate the
1238 				// dispatch loop.
1239 				dispatchNextMessage = false;
1240 			} else {
1241 				PRINT(("LOOPER: fLastMessage: 0x%lx: %.4s\n", fLastMessage->what,
1242 					(char*)&fLastMessage->what));
1243 				DBG(fLastMessage->PrintToStream());
1244 
1245 				//	Get the target handler
1246 				//	Use BMessage friend functions to determine if we are using the
1247 				//	preferred handler, or if a target has been specified
1248 				BHandler* handler;
1249 				if (_use_preferred_target_(fLastMessage)) {
1250 					PRINT(("LOOPER: use preferred target\n"));
1251 					handler = fPreferred;
1252 				} else {
1253 					PRINT(("LOOPER: don't use preferred target\n"));
1254 					/**
1255 						@note	Here is where all the token stuff starts to
1256 								make sense.  How, exactly, do we determine
1257 								what the target BHandler is?  If we look at
1258 								BMessage, we see an int32 field, fTarget.
1259 								Amazingly, we happen to have a global mapping
1260 								of BHandler pointers to int32s!
1261 					 */
1262 					PRINT(("LOOPER: use: %ld\n", _get_message_target_(fLastMessage)));
1263 					gDefaultTokens.GetToken(_get_message_target_(fLastMessage),
1264 						B_HANDLER_TOKEN, (void **)&handler);
1265 					PRINT(("LOOPER: handler: %p, this: %p\n", handler, this));
1266 				}
1267 
1268 				if (!handler) {
1269 					PRINT(("LOOPER: no target handler, use this\n"));
1270 					handler = this;
1271 				}
1272 
1273 				//	Is this a scripting message? (BMessage::HasSpecifiers())
1274 				if (fLastMessage->HasSpecifiers()) {
1275 					int32 index = 0;
1276 					// Make sure the current specifier is kosher
1277 					if (fLastMessage->GetCurrentSpecifier(&index) == B_OK)
1278 						handler = resolve_specifier(handler, fLastMessage);
1279 				} else {
1280 					PRINT(("LOOPER: no scripting message\n"));
1281 				}
1282 
1283 				if (handler) {
1284 					//	Do filtering
1285 					handler = top_level_filter(fLastMessage, handler);
1286 					PRINT(("LOOPER: top_level_filter(): %p\n", handler));
1287 					if (handler && handler->Looper() == this)
1288 						DispatchMessage(fLastMessage, handler);
1289 				}
1290 			}
1291 
1292 			//	Unlock the looper
1293 			Unlock();
1294 
1295 			//	Delete the current message (fLastMessage)
1296 			if (fLastMessage) {
1297 				delete fLastMessage;
1298 				fLastMessage = NULL;
1299 			}
1300 
1301 			//	Are any messages on the port?
1302 			if (port_count(fMsgPort) > 0) {
1303 				//	Do outer loop
1304 				dispatchNextMessage = false;
1305 			}
1306 		}
1307 	}
1308 	PRINT(("BLooper::task_looper() done\n"));
1309 }
1310 
1311 
1312 void
1313 BLooper::do_quit_requested(BMessage *msg)
1314 {
1315 	bool isQuitting = QuitRequested();
1316 
1317 	// We send a reply to the sender, when they're waiting for a reply or
1318 	// if the request message contains a boolean "_shutdown_" field with value
1319 	// true. In the latter case the message came from the registrar, asking
1320 	// the application to shut down.
1321 	bool shutdown;
1322 	if (msg->IsSourceWaiting()
1323 		|| (msg->FindBool("_shutdown_", &shutdown) == B_OK && shutdown)) {
1324 		BMessage ReplyMsg(B_REPLY);
1325 		ReplyMsg.AddBool("result", isQuitting);
1326 		ReplyMsg.AddInt32("thread", fTaskID);
1327 		msg->SendReply(&ReplyMsg);
1328 	}
1329 
1330 	if (isQuitting)
1331 		Quit();
1332 }
1333 
1334 
1335 bool
1336 BLooper::AssertLocked() const
1337 {
1338 	if (!IsLocked()) {
1339 		debugger("looper must be locked before proceeding\n");
1340 		return false;
1341 	}
1342 
1343 	return true;
1344 }
1345 
1346 
1347 BHandler *
1348 BLooper::top_level_filter(BMessage* msg, BHandler* target)
1349 {
1350 	if (msg) {
1351 		// Apply the common filters first
1352 		target = apply_filters(CommonFilterList(), msg, target);
1353 		if (target) {
1354 			if (target->Looper() != this) {
1355 				debugger("Targeted handler does not belong to the looper.");
1356 				target = NULL;
1357 			} else {
1358 				// Now apply handler-specific filters
1359 				target = handler_only_filter(msg, target);
1360 			}
1361 		}
1362 	}
1363 
1364 	return target;
1365 }
1366 
1367 
1368 BHandler *
1369 BLooper::handler_only_filter(BMessage* msg, BHandler* target)
1370 {
1371 	// Keep running filters until our handler is NULL, or until the filtering
1372 	// handler returns itself as the designated handler
1373 	BHandler* oldTarget = NULL;
1374 	while (target != NULL && target != oldTarget) {
1375 		oldTarget = target;
1376 		target = apply_filters(oldTarget->FilterList(), msg, oldTarget);
1377 		if (target && (target->Looper() != this)) {
1378 			debugger("Targeted handler does not belong to the looper.");
1379 			target = NULL;
1380 		}
1381 	}
1382 
1383 	return target;
1384 }
1385 
1386 
1387 BHandler *
1388 BLooper::apply_filters(BList* list, BMessage* msg, BHandler* target)
1389 {
1390 	// This is where the action is!
1391 	// Check the parameters
1392 	if (!list || !msg)
1393 		return target;
1394 
1395 	// For each filter in the provided list
1396 	BMessageFilter* filter = NULL;
1397 	for (int32 i = 0; i < list->CountItems(); ++i) {
1398 		filter = (BMessageFilter*)list->ItemAt(i);
1399 
1400 		// Check command conditions
1401 		if (filter->FiltersAnyCommand() || (filter->Command() == msg->what)) {
1402 			// Check delivery conditions
1403 			message_delivery delivery = filter->MessageDelivery();
1404 			bool dropped = msg->WasDropped();
1405 			if (delivery == B_ANY_DELIVERY ||
1406 				((delivery == B_DROPPED_DELIVERY) && dropped) ||
1407 				((delivery == B_PROGRAMMED_DELIVERY) && !dropped)) {
1408 				// Check source conditions
1409 				message_source source = filter->MessageSource();
1410 				bool remote = msg->IsSourceRemote();
1411 				if (source == B_ANY_SOURCE
1412 					|| (source == B_REMOTE_SOURCE && remote)
1413 					|| (source == B_LOCAL_SOURCE && !remote)) {
1414 					// Are we using an "external" function?
1415 					filter_result result;
1416 					filter_hook func = filter->FilterFunction();
1417 					if (func)
1418 						result = func(msg, &target, filter);
1419 					else
1420 						result = filter->Filter(msg, &target);
1421 
1422 					// Is further processing allowed?
1423 					if (result == B_SKIP_MESSAGE) {
1424 						// No; time to bail out
1425 						return NULL;
1426 					}
1427 				}
1428 			}
1429 		}
1430 	}
1431 
1432 	return target;
1433 }
1434 
1435 
1436 void
1437 BLooper::check_lock()
1438 {
1439 	// This is a cheap variant of AssertLocked()
1440 	// It is used in situations where it's clear that the looper is valid,
1441 	// ie. from handlers
1442 	if (fOwner == -1 || fOwner != find_thread(NULL))
1443 		debugger("Looper must be locked.");
1444 }
1445 
1446 
1447 BHandler *
1448 BLooper::resolve_specifier(BHandler* target, BMessage* msg)
1449 {
1450 	// Check params
1451 	if (!target || !msg)
1452 		return NULL;
1453 
1454 	int32 index;
1455 	BMessage specifier;
1456 	int32 form;
1457 	const char* property;
1458 	status_t err = B_OK;
1459 	BHandler* newTarget = target;
1460 	//	Loop to deal with nested specifiers
1461 	//	(e.g., the 3rd button on the 4th view)
1462 	do {
1463 		err = msg->GetCurrentSpecifier(&index, &specifier, &form, &property);
1464 		if (err) {
1465 			BMessage reply(B_REPLY);
1466 			reply.AddInt32("error", err);
1467 			msg->SendReply(&reply);
1468 			return NULL;
1469 		}
1470 		//	Current target gets what was the new target
1471 		target = newTarget;
1472 		newTarget = target->ResolveSpecifier(msg, index, &specifier, form,
1473 			property);
1474 		//	Check that new target is owned by looper;
1475 		//	use IndexOf() to avoid dereferencing newTarget
1476 		//	(possible race condition with object destruction
1477 		//	by another looper)
1478 		if (!newTarget || IndexOf(newTarget) < 0)
1479 			return NULL;
1480 
1481 		//	Get current specifier index (may change in ResolveSpecifier())
1482 		msg->GetCurrentSpecifier(&index);
1483 	} while (newTarget && newTarget != target && index >= 0);
1484 
1485 	return newTarget;
1486 }
1487 
1488 
1489 void
1490 BLooper::UnlockFully()
1491 {
1492 	AssertLocked();
1493 
1494 /**
1495 	@note	What we're doing here is completely undoing the current owner's lock
1496 			on the looper.  This is actually pretty easy, since the owner only
1497 			has a single aquisition on the semaphore; every subsequent "lock"
1498 			is just an increment to the owner count.  The whole thing is quite
1499 			similar to Unlock(), except that we clear the ownership variables,
1500 			rather than merely decrementing them.
1501  */
1502 	// Clear the owner count
1503 	fOwnerCount = 0;
1504 	// Nobody owns the lock now
1505 	fOwner = -1;
1506 	// There is now one less thread holding a lock on this looper
1507 // bonefish: Currently _Lock() always acquires the semaphore.
1508 /*	long atomicCount = */atomic_add(&fAtomicCount, -1);
1509 //	if (atomicCount > 0)
1510 	{
1511 		release_sem(fLockSem);
1512 	}
1513 }
1514 
1515 
1516 void
1517 BLooper::AddLooper(BLooper *looper)
1518 {
1519 	if (gLooperList.IsLocked())
1520 		gLooperList.AddLooper(looper);
1521 }
1522 
1523 
1524 bool
1525 BLooper::IsLooperValid(const BLooper *looper)
1526 {
1527 	if (gLooperList.IsLocked())
1528 		return gLooperList.IsLooperValid(looper);
1529 
1530 	return false;
1531 }
1532 
1533 
1534 void
1535 BLooper::RemoveLooper(BLooper *looper)
1536 {
1537 	if (gLooperList.IsLocked())
1538 		gLooperList.RemoveLooper(looper);
1539 }
1540 
1541 
1542 void
1543 BLooper::GetLooperList(BList* list)
1544 {
1545 	BObjectLocker<BLooperList> ListLock(gLooperList);
1546 	if (ListLock.IsLocked())
1547 		gLooperList.GetLooperList(list);
1548 }
1549 
1550 
1551 BLooper *
1552 BLooper::LooperForName(const char* name)
1553 {
1554 	if (gLooperList.IsLocked())
1555 		return gLooperList.LooperForName(name);
1556 
1557 	return NULL;
1558 }
1559 
1560 
1561 BLooper *
1562 BLooper::LooperForPort(port_id port)
1563 {
1564 	if (gLooperList.IsLocked())
1565 		return gLooperList.LooperForPort(port);
1566 
1567 	return NULL;
1568 }
1569 
1570 
1571 //	#pragma mark -
1572 
1573 
1574 port_id
1575 _get_looper_port_(const BLooper *looper)
1576 {
1577 	return looper->fMsgPort;
1578 }
1579 
1580