void TCPListenerSocket::ProcessEvent(int /*eventBits*/) { //we are executing on the same thread as every other //socket, so whatever you do here has to be fast. struct sockaddr_in addr; #if __Win32__ || __osf__ || __sgi__ || __hpux__ int size = sizeof(addr); #else socklen_t size = sizeof(addr); #endif Task* theTask = NULL; TCPSocket* theSocket = NULL; //fSocket data member of TCPSocket. int osSocket = accept(fFileDesc, (struct sockaddr*)&addr, &size); //test osSocket = -1; if (osSocket == -1) { //take a look at what this error is. int acceptError = OSThread::GetErrno(); if (acceptError == EAGAIN) { //If it's EAGAIN, there's nothing on the listen queue right now, //so modwatch and return this->RequestEvent(EV_RE); return; } //test acceptError = ENFILE; //test acceptError = EINTR; //test acceptError = ENOENT; //if these error gets returned, we're out of file desciptors, //the server is going to be failing on sockets, logs, qtgroups and qtuser auth file accesses and movie files. The server is not functional. if (acceptError == EMFILE || acceptError == ENFILE) { #ifndef __Win32__ QTSSModuleUtils::LogErrorStr(qtssFatalVerbosity, "Out of File Descriptors. Set max connections lower and check for competing usage from other processes. Exiting."); #endif exit (EXIT_FAILURE); } else { char errStr[256]; errStr[sizeof(errStr) -1] = 0; qtss_snprintf(errStr, sizeof(errStr) -1, "accept error = %d '%s' on socket. Clean up and continue.", acceptError, strerror(acceptError)); WarnV( (acceptError == 0), errStr); theTask = this->GetSessionTask(&theSocket); if (theTask == NULL) { close(osSocket); } else { theTask->Signal(Task::kKillEvent); // just clean up the task } if (theSocket) theSocket->fState &= ~kConnected; // turn off connected state return; } } theTask = this->GetSessionTask(&theSocket); if (theTask == NULL) { //this should be a disconnect. do an ioctl call? close(osSocket); if (theSocket) theSocket->fState &= ~kConnected; // turn off connected state } else { Assert(osSocket != EventContext::kInvalidFileDesc); //set options on the socket //we are a server, always disable nagle algorithm int one = 1; int err = ::setsockopt(osSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(int)); AssertV(err == 0, OSThread::GetErrno()); err = ::setsockopt(osSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(int)); AssertV(err == 0, OSThread::GetErrno()); int sndBufSize = 96L * 1024L; err = ::setsockopt(osSocket, SOL_SOCKET, SO_SNDBUF, (char*)&sndBufSize, sizeof(int)); AssertV(err == 0, OSThread::GetErrno()); //setup the socket. When there is data on the socket, //theTask will get an kReadEvent event theSocket->Set(osSocket, &addr); theSocket->InitNonBlocking(osSocket); theSocket->SetTask(theTask); theSocket->RequestEvent(EV_RE); theTask->SetThreadPicker(Task::GetBlockingTaskThreadPicker()); //The RTSP Task processing threads } if (fSleepBetweenAccepts) { // We are at our maximum supported sockets // slow down so we have time to process the active ones (we will respond with errors or service). // wake up and execute again after sleeping. The timer must be reset each time through //qtss_printf("TCPListenerSocket slowing down\n"); this->SetIdleTimer(kTimeBetweenAcceptsInMsec); //sleep 1 second } else { // sleep until there is a read event outstanding (another client wants to connect) //qtss_printf("TCPListenerSocket normal speed\n"); this->RequestEvent(EV_RE); } fOutOfDescriptors = false; // always false for now we don't properly handle this elsewhere in the code }
// You know the packet type and just want to parse it now bool RTCPCompressedQTSSPacket::ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength) { if (!this->ParseCompressedQTSSPacket(inPacketBuffer, inPacketLength)) return false; APPEND_TO_DUMP_ARRAY("%s", "\n RTCP APP QTSS Report "); char printName[5]; (void) this->GetAppPacketName(printName, sizeof(printName)); APPEND_TO_DUMP_ARRAY("H_app_packet_name = %s, ", printName); APPEND_TO_DUMP_ARRAY("H_ssrc = %" _U32BITARG_ ", ", this->GetPacketSSRC()); APPEND_TO_DUMP_ARRAY("H_src_ID = %" _U32BITARG_ ", ", this->GetQTSSReportSourceID()); APPEND_TO_DUMP_ARRAY("H_vers=%d, ", this->GetQTSSPacketVersion()); APPEND_TO_DUMP_ARRAY("H_packt_len=%d", this->GetQTSSPacketLength()); UInt8* qtssDataBuffer = this->GetPacketBuffer() + kQTSSDataOffset; //packet length is given in words UInt32 bytesRemaining = this->GetQTSSPacketLength() * 4; while (bytesRemaining >= 4) //items must be at least 32 bits { // DMS - There is no guarentee that qtssDataBuffer will be 4 byte aligned, because // individual APP packet fields can be 6 bytes or 4 bytes or 8 bytes. So we have to // use the 4-byte align protection functions. Sparc and MIPS processors will crash otherwise UInt32 theHeader = ntohl(OS::GetUInt32FromMemory((UInt32*)&qtssDataBuffer[kQTSSItemTypeOffset])); UInt16 itemType = (UInt16)((theHeader & kQTSSItemTypeMask) >> kQTSSItemTypeShift); UInt8 itemVersion = (UInt8)((theHeader & kQTSSItemVersionMask) >> kQTSSItemVersionShift); UInt8 itemLengthInBytes = (UInt8)(theHeader & kQTSSItemLengthMask); APPEND_TO_DUMP_ARRAY("\n h_type=%.2s(", (char*)&itemType); APPEND_TO_DUMP_ARRAY(", h_vers=%u", itemVersion); APPEND_TO_DUMP_ARRAY(", h_size=%u", itemLengthInBytes); qtssDataBuffer += sizeof(UInt32); //advance past the above UInt16's & UInt8's (point it at the actual item data) //Update bytesRemaining (move it past current item) //This itemLengthInBytes is part of the packet and could therefore be bogus. //Make sure not to overstep the end of the buffer! bytesRemaining -= sizeof(UInt32); if (itemLengthInBytes > bytesRemaining) break; //don't walk off the end of the buffer //itemLengthInBytes = bytesRemaining; bytesRemaining -= itemLengthInBytes; switch (itemType) { case TW0_CHARS_TO_INT('r', 'r'): //'rr': //'rrcv': { fReceiverBitRate = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); qtssDataBuffer += sizeof(fReceiverBitRate); APPEND_TO_DUMP_ARRAY(", rcvr_bit_rate=%" _U32BITARG_ "", fReceiverBitRate); } break; case TW0_CHARS_TO_INT('l', 't'): //'lt': //'late': { fAverageLateMilliseconds = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fAverageLateMilliseconds); APPEND_TO_DUMP_ARRAY(", avg_late=%u", fAverageLateMilliseconds); } break; case TW0_CHARS_TO_INT('l', 's'): // 'ls': //'loss': { fPercentPacketsLost = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fPercentPacketsLost); APPEND_TO_DUMP_ARRAY(", percent_loss=%u", fPercentPacketsLost); } break; case TW0_CHARS_TO_INT('d', 'l'): //'dl': //'bdly': { fAverageBufferDelayMilliseconds = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fAverageBufferDelayMilliseconds); APPEND_TO_DUMP_ARRAY(", avg_buf_delay=%u", fAverageBufferDelayMilliseconds); } break; case TW0_CHARS_TO_INT(':', ')'): //:) { fIsGettingBetter = true; APPEND_TO_DUMP_ARRAY(", quality=%s", "better"); } break; case TW0_CHARS_TO_INT(':', '('): // ':(': { fIsGettingWorse = true; APPEND_TO_DUMP_ARRAY(", quality=%s", "worse"); } break; case TW0_CHARS_TO_INT(':', '|'): // ':|': { fIsGettingWorse = true; APPEND_TO_DUMP_ARRAY(", quality=%s", "same"); } break; case TW0_CHARS_TO_INT('e', 'y'): //'ey': //'eyes': { fNumEyes = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); qtssDataBuffer += sizeof(fNumEyes); APPEND_TO_DUMP_ARRAY(", eyes=%" _U32BITARG_ "", fNumEyes); if (itemLengthInBytes >= 2) { fNumEyesActive = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); qtssDataBuffer += sizeof(fNumEyesActive); APPEND_TO_DUMP_ARRAY(", eyes_actv=%" _U32BITARG_ "", fNumEyesActive); } if (itemLengthInBytes >= 3) { fNumEyesPaused = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); qtssDataBuffer += sizeof(fNumEyesPaused); APPEND_TO_DUMP_ARRAY(", eyes_pausd=%" _U32BITARG_ "", fNumEyesPaused); } } break; case TW0_CHARS_TO_INT('p', 'r'): // 'pr': //'prcv': { fTotalPacketsReceived = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); qtssDataBuffer += sizeof(fTotalPacketsReceived); APPEND_TO_DUMP_ARRAY(", pckts_rcvd=%" _U32BITARG_ "", fTotalPacketsReceived); } break; case TW0_CHARS_TO_INT('p', 'd'): //'pd': //'pdrp': { fTotalPacketsDropped = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fTotalPacketsDropped); APPEND_TO_DUMP_ARRAY(", pckts_drppd=%u", fTotalPacketsDropped); } break; case TW0_CHARS_TO_INT('p', 'l'): //'pl': //'p???': { fTotalPacketsLost = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fTotalPacketsLost); APPEND_TO_DUMP_ARRAY(", ttl_pckts_lost=%u", fTotalPacketsLost); } break; case TW0_CHARS_TO_INT('b', 'l'): //'bl': //'bufl': { fClientBufferFill = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fClientBufferFill); APPEND_TO_DUMP_ARRAY(", buffr_fill=%u", fClientBufferFill); } break; case TW0_CHARS_TO_INT('f', 'r'): //'fr': //'frat': { fFrameRate = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fFrameRate); APPEND_TO_DUMP_ARRAY(", frame_rate=%u", fFrameRate); } break; case TW0_CHARS_TO_INT('x', 'r'): //'xr': //'xrat': { fExpectedFrameRate = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fExpectedFrameRate); APPEND_TO_DUMP_ARRAY(", xpectd_frame_rate=%u", fExpectedFrameRate); } break; case TW0_CHARS_TO_INT('d', '#'): //'d#': //'dry#': { fAudioDryCount = ntohs(*(UInt16*)qtssDataBuffer); qtssDataBuffer += sizeof(fAudioDryCount); APPEND_TO_DUMP_ARRAY(", aud_dry_count=%u", fAudioDryCount); } break; case TW0_CHARS_TO_INT('o', 'b'): //'ob': // overbuffer window size { fOverbufferWindowSize = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); qtssDataBuffer += sizeof(fOverbufferWindowSize); APPEND_TO_DUMP_ARRAY(", ovr_buffr_windw_siz=%" _U32BITARG_ "", fOverbufferWindowSize); } break; default: { if (fDebug) { char s[12] = ""; qtss_sprintf(s, " [%.2s]", (char*)&itemType); WarnV(false, "Unknown APP('QTSS') item type"); WarnV(false, s); } } break; } // switch (itemType) APPEND_TO_DUMP_ARRAY("%s", "), "); } //while ( bytesRemaining >= 4 ) return true; }
SInt64 RTPStatsUpdaterTask::Run() { QTSServerInterface* theServer = QTSServerInterface::sServer; // All of this must happen atomically wrt dictionary values we are manipulating OSMutexLocker locker(&theServer->fMutex); //First update total bytes. This must be done because total bytes is a 64 bit number, //so no atomic functions can apply. // // NOTE: The line below is not thread safe on non-PowerPC platforms. This is // because the fPeriodicRTPBytes variable is being manipulated from within an // atomic_add. On PowerPC, assignments are atomic, so the assignment below is ok. // On a non-PowerPC platform, the following would be thread safe: //unsigned int periodicBytes = atomic_add(&theServer->fPeriodicRTPBytes, 0); unsigned int periodicBytes = theServer->fPeriodicRTPBytes; (void)atomic_sub(&theServer->fPeriodicRTPBytes, periodicBytes); theServer->fTotalRTPBytes += periodicBytes; // Same deal for packet totals unsigned int periodicPackets = theServer->fPeriodicRTPPackets; (void)atomic_sub(&theServer->fPeriodicRTPPackets, periodicPackets); theServer->fTotalRTPPackets += periodicPackets; // ..and for lost packet totals unsigned int periodicPacketsLost = theServer->fPeriodicRTPPacketsLost; (void)atomic_sub(&theServer->fPeriodicRTPPacketsLost, periodicPacketsLost); theServer->fTotalRTPPacketsLost += periodicPacketsLost; SInt64 curTime = OS::Milliseconds(); //for cpu percent Float32 cpuTimeInSec = GetCPUTimeInSeconds(); //also update current bandwidth statistic if (fLastBandwidthTime != 0) { Assert(curTime > fLastBandwidthTime); UInt32 delta = (UInt32)(curTime - fLastBandwidthTime); if (delta < 1000) WarnV(delta >= 1000, "Timer is off"); //do the bandwidth computation using floating point divides //for accuracy and speed. Float32 bits = (Float32)(periodicBytes * 8); Float32 theTime = (Float32)delta; theTime /= 1000; bits /= theTime; Assert(bits >= 0); theServer->fCurrentRTPBandwidthInBits = (UInt32)bits; // okay let's do it for MP3 bytes now bits = (Float32)(((SInt64)theServer->fTotalMP3Bytes - fLastTotalMP3Bytes) * 8); bits /= theTime; theServer->fCurrentMP3BandwidthInBits = (UInt32)bits; //do the same computation for packets per second Float32 packetsPerSecond = (Float32)periodicPackets; packetsPerSecond /= theTime; Assert(packetsPerSecond >= 0); theServer->fRTPPacketsPerSecond = (UInt32)packetsPerSecond; //do the computation for cpu percent Float32 diffTime = cpuTimeInSec - theServer->fCPUTimeUsedInSec; theServer->fCPUPercent = (diffTime/theTime) * 100; UInt32 numProcessors = OS::GetNumProcessors(); if (numProcessors > 1) theServer->fCPUPercent /= numProcessors; } fLastTotalMP3Bytes = (SInt64)theServer->fTotalMP3Bytes; fLastBandwidthTime = curTime; // We use a running average for avg. bandwidth calculations theServer->fAvgMP3BandwidthInBits = (theServer->fAvgMP3BandwidthInBits + theServer->fCurrentMP3BandwidthInBits)/2; //for cpu percent theServer->fCPUTimeUsedInSec = cpuTimeInSec; //also compute average bandwidth, a much more smooth value. This is done with //the fLastBandwidthAvg, a timestamp of the last time we did an average, and //fLastBytesSent, the number of bytes sent when we last did an average. if ((fLastBandwidthAvg != 0) && (curTime > (fLastBandwidthAvg + (theServer->GetPrefs()->GetAvgBandwidthUpdateTimeInSecs() * 1000)))) { UInt32 delta = (UInt32)(curTime - fLastBandwidthAvg); SInt64 bytesSent = theServer->fTotalRTPBytes - fLastBytesSent; Assert(bytesSent >= 0); //do the bandwidth computation using floating point divides //for accuracy and speed. Float32 bits = (Float32)(bytesSent * 8); Float32 theAvgTime = (Float32)delta; theAvgTime /= 1000; bits /= theAvgTime; Assert(bits >= 0); theServer->fAvgRTPBandwidthInBits = (UInt32)bits; fLastBandwidthAvg = curTime; fLastBytesSent = theServer->fTotalRTPBytes; //if the bandwidth is above the bandwidth setting, disconnect 1 user by sending them //a BYE RTCP packet. SInt32 maxKBits = theServer->GetPrefs()->GetMaxKBitsBandwidth(); if ((maxKBits > -1) && (theServer->fAvgRTPBandwidthInBits > ((UInt32)maxKBits * 1024))) { //we need to make sure that all of this happens atomically wrt the session map OSMutexLocker locker(theServer->GetRTPSessionMap()->GetMutex()); RTPSessionInterface* theSession = this->GetNewestSession(theServer->fRTPMap); if (theSession != NULL) if ((curTime - theSession->GetSessionCreateTime()) < theServer->GetPrefs()->GetSafePlayDurationInSecs() * 1000) theSession->Signal(Task::kKillEvent); } } else if (fLastBandwidthAvg == 0) { fLastBandwidthAvg = curTime; fLastBytesSent = theServer->fTotalRTPBytes; } (void)this->GetEvents();//we must clear the event mask! return theServer->GetPrefs()->GetTotalBytesUpdateTimeInSecs() * 1000; }
/* 在TCPListenerSocket中,ProcessEvent 函数(继承EventContext 的ProcessEvent()函数)被重载用来创建Socket和Task 对象得配对 */ void TCPListenerSocket::ProcessEvent(int /*eventBits*/) { //we are executing on the same thread as every other //socket, so whatever you do here has to be fast. /* 该函数运行于系统唯一的EventThread 线程中,所以要尽量快速,以免占用过多的系统资源 */ /* 在accept()中存放接受的远处客户端的ip地址 */ struct sockaddr_in addr; socklen_t size = sizeof(addr); /* 注意theTask(通过派生类TCPSocket)和theSocket都是TCPListenerSocket的基类 */ /**************** 注意:通过子类重载GetSessionTask()使Task(具体说,是RTSPSession)和TCPSocket配对 ***********************/ Task* theTask = NULL; TCPSocket* theSocket = NULL; //fSocket data member of TCPSocket. /* 服务器端的Socket接受客户端的Socket的连接请求,成功后返回服务器新建的接受连接的Socket的描述符,否则返回INVALID_SOCKET */ int osSocket = accept(fFileDesc, (struct sockaddr*)&addr, &size); //test osSocket = -1; /* 假如出错了,进行错误处理.以下全是错误处理!! */ if (osSocket == -1) { //take a look at what this error is. /* 获取具体的出错原因 */ int acceptError = OSThread::GetErrno(); /* 对得出的错误分情形讨论: */ //test acceptError = EAGAIN; if (acceptError == EAGAIN) { //If it's EAGAIN, there's nothing on the listen queue right now, //so modwatch and return /* 在同一socket端口上请求监听指定的EV_RE事件 */ this->RequestEvent(EV_RE); return; } //test acceptError = ENFILE; //test acceptError = EINTR; //test acceptError = ENOENT; //if these error gets returned, we're out of file descriptors, //the server is going to be failing on sockets, logs, qtgroups and qtuser auth file accesses and movie files. The server is not functional. // 文件描述符用光,这时服务器会丧失功能,直接退出 if (acceptError == EMFILE || acceptError == ENFILE) { QTSSModuleUtils::LogErrorStr(qtssFatalVerbosity, "Out of File Descriptors. Set max connections lower and check for competing usage from other processes. Exiting."); exit (EXIT_FAILURE); } else //假如是其它错误(除EAGAIN\EMFILE\ENFILE以外的),在屏幕上显示错误信息,同时将配对的任务和新建的TCPSocket分别删除和关闭,并返回 { char errStr[256]; /* 确保末尾null-terminated */ errStr[sizeof(errStr) -1] = 0; /* 得到指定格式的errStr形如"accept error = 1 '*****error' on socket. Clean up and continue." */ qtss_snprintf(errStr, sizeof(errStr) -1, "accept error = %d '%s' on socket. Clean up and continue.", acceptError, strerror(acceptError)); /* 当条件不成立时,在屏幕上显示错误信息 */ WarnV( (acceptError == 0), errStr); /**************** 注意:通过子类重载GetSessionTask()使Task和TCPSocket配对 ***********************/ /* 用RTSPListenerSocket::GetSessionTask()获取Session Task和socket,并对结果分析 */ theTask = this->GetSessionTask(&theSocket); if (theTask == NULL) { /* 若没有获取任务,就关闭Socket */ close(osSocket); } else { theTask->Signal(Task::kKillEvent); // just clean up the task } /* 假如RTSPSession中相对应的theSocket非空,就将其状态设为非连接 */ if (theSocket) theSocket->fState &= ~kConnected; // turn off connected state return; } }/* errors handling */ /* 得到Session Task并作错误处理 */ /* 注意以后的派生类(当是RTSPListenerSocket)去获取任务,并设置好Task和outSocket */ /**************** 注意:通过子类重载GetSessionTask()使Task和TCPSocket配对 ***********************/ theTask = this->GetSessionTask(&theSocket); /* 如果没有获得任务,将已接受连接的服务器端的osSocket关闭,将服务器上RTSPSession中相对应的theSocket关闭 */ if (theTask == NULL) { //this should be a disconnect. do an ioctl call? close(osSocket); if (theSocket) theSocket->fState &= ~kConnected; // turn off connected state } /* 假如成功获取到任务,就分别设置这相对应的两个Socket的相关属性 */ else//创建Task成功,接着创建Socket 对象 { /* 确保接受连接的服务器端Socket不是初始状态 */ Assert(osSocket != EventContext::kInvalidFileDesc); //set options on the socket //we are a server, always disable NAGLE ALGORITHM /* 设置接受连接的服务器端Socket是:非延迟的,保持活跃,指定大小的传送缓存 */ int one = 1; int err = ::setsockopt(osSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(int)); AssertV(err == 0, OSThread::GetErrno()); err = ::setsockopt(osSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(int)); AssertV(err == 0, OSThread::GetErrno()); /* 设置服务器端传送缓存大小96K字节 */ int sndBufSize = 96L * 1024L; err = ::setsockopt(osSocket, SOL_SOCKET, SO_SNDBUF, (char*)&sndBufSize, sizeof(int)); AssertV(err == 0, OSThread::GetErrno()); //setup the socket. When there is data on the socket, theTask will get an kReadEvent event // /* 用服务器接受连接时新建的Socket和连接它的客户端的IP地址等初始化RTSPSession中的TCPSocket数据成员 */ theSocket->Set(osSocket, &addr); /* 设置RTSPSession中的TCPSocket为非阻塞的 */ theSocket->InitNonBlocking(osSocket); /**************** 注意:通过子类重载GetSessionTask()使Task和TCPSocket配对 ***********************/ /* 给RTSPSession中的TCPSocket数据成员设定任务,就是它所在的RTSPSession对象实例,使RTSPSession和TCPSocket紧密配对配对 */ theSocket->SetTask(theTask); // 完成上述设置后,刚建立连接的这个RTSPSession对象实例的TCPSocket向TaskThread请求读入Client发送的数据 theSocket->RequestEvent(EV_RE); } /* 在两次accept()间休眠吗?进行速度调整! */ if (fSleepBetweenAccepts) { // We are at our maximum supported sockets // slow down so we have time to process the active ones (we will respond with errors or service). // wake up and execute again after sleeping. The timer must be reset each time through //qtss_printf("TCPListenerSocket slowing down\n"); // 我们已经用光文件描述符,此处需要设置空闲任务计时器,让当前线程休眠1s this->SetIdleTimer(kTimeBetweenAcceptsInMsec); //sleep 1 second } else { // sleep until there is a read event outstanding (another client wants to connect) //qtss_printf("TCPListenerSocket normal speed\n"); //处理完一次连接请求后,服务器端的侦听TCPListenerSocket对象还要接着监听,等待接入新的Client连接 this->RequestEvent(EV_RE); } fOutOfDescriptors = false; // always false for now we don't properly handle this elsewhere in the code }