Index: webrtc/libjingle/session/tunnel/pseudotcpchannel.cc |
diff --git a/webrtc/libjingle/session/tunnel/pseudotcpchannel.cc b/webrtc/libjingle/session/tunnel/pseudotcpchannel.cc |
deleted file mode 100644 |
index c5b9443253c41c2fa7db5ce24fc83657d5f4d621..0000000000000000000000000000000000000000 |
--- a/webrtc/libjingle/session/tunnel/pseudotcpchannel.cc |
+++ /dev/null |
@@ -1,605 +0,0 @@ |
-/* |
- * libjingle |
- * Copyright 2004--2006, Google Inc. |
- * |
- * Redistribution and use in source and binary forms, with or without |
- * modification, are permitted provided that the following conditions are met: |
- * |
- * 1. Redistributions of source code must retain the above copyright notice, |
- * this list of conditions and the following disclaimer. |
- * 2. Redistributions in binary form must reproduce the above copyright notice, |
- * this list of conditions and the following disclaimer in the documentation |
- * and/or other materials provided with the distribution. |
- * 3. The name of the author may not be used to endorse or promote products |
- * derived from this software without specific prior written permission. |
- * |
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
- */ |
- |
-#include <algorithm> |
-#include <string> |
- |
-#include "pseudotcpchannel.h" |
-#include "webrtc/p2p/base/candidate.h" |
-#include "webrtc/p2p/base/transportchannel.h" |
-#include "webrtc/base/basictypes.h" |
-#include "webrtc/base/common.h" |
-#include "webrtc/base/logging.h" |
-#include "webrtc/base/scoped_ptr.h" |
-#include "webrtc/base/stringutils.h" |
- |
-using namespace rtc; |
- |
-namespace cricket { |
- |
-extern const rtc::ConstantLabel SESSION_STATES[]; |
- |
-// MSG_WK_* - worker thread messages |
-// MSG_ST_* - stream thread messages |
-// MSG_SI_* - signal thread messages |
- |
-enum { |
- MSG_WK_CLOCK = 1, |
- MSG_WK_PURGE, |
- MSG_ST_EVENT, |
- MSG_SI_DESTROYCHANNEL, |
- MSG_SI_DESTROY, |
-}; |
- |
-struct EventData : public MessageData { |
- int event, error; |
- EventData(int ev, int err = 0) : event(ev), error(err) { } |
-}; |
- |
-/////////////////////////////////////////////////////////////////////////////// |
-// PseudoTcpChannel::InternalStream |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-class PseudoTcpChannel::InternalStream : public StreamInterface { |
-public: |
- InternalStream(PseudoTcpChannel* parent); |
- virtual ~InternalStream(); |
- |
- virtual StreamState GetState() const; |
- virtual StreamResult Read(void* buffer, size_t buffer_len, |
- size_t* read, int* error); |
- virtual StreamResult Write(const void* data, size_t data_len, |
- size_t* written, int* error); |
- virtual void Close(); |
- |
-private: |
- // parent_ is accessed and modified exclusively on the event thread, to |
- // avoid thread contention. This means that the PseudoTcpChannel cannot go |
- // away until after it receives a Close() from TunnelStream. |
- PseudoTcpChannel* parent_; |
-}; |
- |
-/////////////////////////////////////////////////////////////////////////////// |
-// PseudoTcpChannel |
-// Member object lifetime summaries: |
-// session_ - passed in constructor, cleared when channel_ goes away. |
-// channel_ - created in Connect, destroyed when session_ or tcp_ goes away. |
-// tcp_ - created in Connect, destroyed when channel_ goes away, or connection |
-// closes. |
-// worker_thread_ - created when channel_ is created, purged when channel_ is |
-// destroyed. |
-// stream_ - created in GetStream, destroyed by owner at arbitrary time. |
-// this - created in constructor, destroyed when worker_thread_ and stream_ |
-// are both gone. |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-// |
-// Signal thread methods |
-// |
- |
-PseudoTcpChannel::PseudoTcpChannel(Thread* stream_thread, Session* session) |
- : signal_thread_(session->session_manager()->signaling_thread()), |
- worker_thread_(NULL), |
- stream_thread_(stream_thread), |
- session_(session), channel_(NULL), tcp_(NULL), stream_(NULL), |
- stream_readable_(false), pending_read_event_(false), |
- ready_to_connect_(false) { |
- ASSERT(signal_thread_->IsCurrent()); |
- ASSERT(NULL != session_); |
-} |
- |
-PseudoTcpChannel::~PseudoTcpChannel() { |
- ASSERT(signal_thread_->IsCurrent()); |
- ASSERT(worker_thread_ == NULL); |
- ASSERT(session_ == NULL); |
- ASSERT(channel_ == NULL); |
- ASSERT(stream_ == NULL); |
- ASSERT(tcp_ == NULL); |
-} |
- |
-bool PseudoTcpChannel::Connect(const std::string& content_name, |
- const std::string& channel_name, |
- int component) { |
- ASSERT(signal_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- |
- if (channel_) |
- return false; |
- |
- ASSERT(session_ != NULL); |
- worker_thread_ = session_->session_manager()->worker_thread(); |
- content_name_ = content_name; |
- channel_ = session_->CreateChannel( |
- content_name, channel_name, component); |
- channel_name_ = channel_name; |
- channel_->SetOption(Socket::OPT_DONTFRAGMENT, 1); |
- |
- channel_->SignalDestroyed.connect(this, |
- &PseudoTcpChannel::OnChannelDestroyed); |
- channel_->SignalWritableState.connect(this, |
- &PseudoTcpChannel::OnChannelWritableState); |
- channel_->SignalReadPacket.connect(this, |
- &PseudoTcpChannel::OnChannelRead); |
- channel_->SignalRouteChange.connect(this, |
- &PseudoTcpChannel::OnChannelConnectionChanged); |
- |
- ASSERT(tcp_ == NULL); |
- tcp_ = new PseudoTcp(this, 0); |
- if (session_->initiator()) { |
- // Since we may try several protocols and network adapters that won't work, |
- // waiting until we get our first writable notification before initiating |
- // TCP negotiation. |
- ready_to_connect_ = true; |
- } |
- |
- return true; |
-} |
- |
-StreamInterface* PseudoTcpChannel::GetStream() { |
- ASSERT(signal_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- ASSERT(NULL != session_); |
- if (!stream_) |
- stream_ = new PseudoTcpChannel::InternalStream(this); |
- //TODO("should we disallow creation of new stream at some point?"); |
- return stream_; |
-} |
- |
-void PseudoTcpChannel::OnChannelDestroyed(TransportChannel* channel) { |
- LOG_F(LS_INFO) << "(" << channel->component() << ")"; |
- ASSERT(signal_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- ASSERT(channel == channel_); |
- signal_thread_->Clear(this, MSG_SI_DESTROYCHANNEL); |
- // When MSG_WK_PURGE is received, we know there will be no more messages from |
- // the worker thread. |
- worker_thread_->Clear(this, MSG_WK_CLOCK); |
- worker_thread_->Post(this, MSG_WK_PURGE); |
- session_ = NULL; |
- channel_ = NULL; |
- if ((stream_ != NULL) |
- && ((tcp_ == NULL) || (tcp_->State() != PseudoTcp::TCP_CLOSED))) |
- stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, 0)); |
- if (tcp_) { |
- tcp_->Close(true); |
- AdjustClock(); |
- } |
- SignalChannelClosed(this); |
-} |
- |
-void PseudoTcpChannel::OnSessionTerminate(Session* session) { |
- // When the session terminates before we even connected |
- CritScope lock(&cs_); |
- if (session_ != NULL && channel_ == NULL) { |
- ASSERT(session == session_); |
- ASSERT(worker_thread_ == NULL); |
- ASSERT(tcp_ == NULL); |
- LOG(LS_INFO) << "Destroying unconnected PseudoTcpChannel"; |
- session_ = NULL; |
- if (stream_ != NULL) |
- stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, -1)); |
- } |
- |
- // Even though session_ is being destroyed, we mustn't clear the pointer, |
- // since we'll need it to tear down channel_. |
- // |
- // TODO: Is it always the case that if channel_ != NULL then we'll get |
- // a channel-destroyed notification? |
-} |
- |
-void PseudoTcpChannel::GetOption(PseudoTcp::Option opt, int* value) { |
- ASSERT(signal_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- ASSERT(tcp_ != NULL); |
- tcp_->GetOption(opt, value); |
-} |
- |
-void PseudoTcpChannel::SetOption(PseudoTcp::Option opt, int value) { |
- ASSERT(signal_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- ASSERT(tcp_ != NULL); |
- tcp_->SetOption(opt, value); |
-} |
- |
-// |
-// Stream thread methods |
-// |
- |
-StreamState PseudoTcpChannel::GetState() const { |
- ASSERT(stream_ != NULL && stream_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- if (!session_) |
- return SS_CLOSED; |
- if (!tcp_) |
- return SS_OPENING; |
- switch (tcp_->State()) { |
- case PseudoTcp::TCP_LISTEN: |
- case PseudoTcp::TCP_SYN_SENT: |
- case PseudoTcp::TCP_SYN_RECEIVED: |
- return SS_OPENING; |
- case PseudoTcp::TCP_ESTABLISHED: |
- return SS_OPEN; |
- case PseudoTcp::TCP_CLOSED: |
- default: |
- return SS_CLOSED; |
- } |
-} |
- |
-StreamResult PseudoTcpChannel::Read(void* buffer, size_t buffer_len, |
- size_t* read, int* error) { |
- ASSERT(stream_ != NULL && stream_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- if (!tcp_) |
- return SR_BLOCK; |
- |
- stream_readable_ = false; |
- int result = tcp_->Recv(static_cast<char*>(buffer), buffer_len); |
- //LOG_F(LS_VERBOSE) << "Recv returned: " << result; |
- if (result > 0) { |
- if (read) |
- *read = result; |
- // PseudoTcp doesn't currently support repeated Readable signals. Simulate |
- // them here. |
- stream_readable_ = true; |
- if (!pending_read_event_) { |
- pending_read_event_ = true; |
- stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ), true); |
- } |
- return SR_SUCCESS; |
- } else if (IsBlockingError(tcp_->GetError())) { |
- return SR_BLOCK; |
- } else { |
- if (error) |
- *error = tcp_->GetError(); |
- return SR_ERROR; |
- } |
- // This spot is never reached. |
-} |
- |
-StreamResult PseudoTcpChannel::Write(const void* data, size_t data_len, |
- size_t* written, int* error) { |
- ASSERT(stream_ != NULL && stream_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- if (!tcp_) |
- return SR_BLOCK; |
- int result = tcp_->Send(static_cast<const char*>(data), data_len); |
- //LOG_F(LS_VERBOSE) << "Send returned: " << result; |
- if (result > 0) { |
- if (written) |
- *written = result; |
- return SR_SUCCESS; |
- } else if (IsBlockingError(tcp_->GetError())) { |
- return SR_BLOCK; |
- } else { |
- if (error) |
- *error = tcp_->GetError(); |
- return SR_ERROR; |
- } |
- // This spot is never reached. |
-} |
- |
-void PseudoTcpChannel::Close() { |
- ASSERT(stream_ != NULL && stream_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- stream_ = NULL; |
- // Clear out any pending event notifications |
- stream_thread_->Clear(this, MSG_ST_EVENT); |
- if (tcp_) { |
- tcp_->Close(false); |
- AdjustClock(); |
- } else { |
- CheckDestroy(); |
- } |
-} |
- |
-// |
-// Worker thread methods |
-// |
- |
-void PseudoTcpChannel::OnChannelWritableState(TransportChannel* channel) { |
- LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]"; |
- ASSERT(worker_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- if (!channel_) { |
- LOG_F(LS_WARNING) << "NULL channel"; |
- return; |
- } |
- ASSERT(channel == channel_); |
- if (!tcp_) { |
- LOG_F(LS_WARNING) << "NULL tcp"; |
- return; |
- } |
- if (!ready_to_connect_ || !channel->writable()) |
- return; |
- |
- ready_to_connect_ = false; |
- tcp_->Connect(); |
- AdjustClock(); |
-} |
- |
-void PseudoTcpChannel::OnChannelRead(TransportChannel* channel, |
- const char* data, size_t size, |
- const rtc::PacketTime& packet_time, |
- int flags) { |
- //LOG_F(LS_VERBOSE) << "(" << size << ")"; |
- ASSERT(worker_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- if (!channel_) { |
- LOG_F(LS_WARNING) << "NULL channel"; |
- return; |
- } |
- ASSERT(channel == channel_); |
- if (!tcp_) { |
- LOG_F(LS_WARNING) << "NULL tcp"; |
- return; |
- } |
- tcp_->NotifyPacket(data, size); |
- AdjustClock(); |
-} |
- |
-void PseudoTcpChannel::OnChannelConnectionChanged(TransportChannel* channel, |
- const Candidate& candidate) { |
- LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]"; |
- ASSERT(worker_thread_->IsCurrent()); |
- CritScope lock(&cs_); |
- if (!channel_) { |
- LOG_F(LS_WARNING) << "NULL channel"; |
- return; |
- } |
- ASSERT(channel == channel_); |
- if (!tcp_) { |
- LOG_F(LS_WARNING) << "NULL tcp"; |
- return; |
- } |
- |
- uint16 mtu = 1280; // safe default |
- int family = candidate.address().family(); |
- Socket* socket = |
- worker_thread_->socketserver()->CreateAsyncSocket(family, SOCK_DGRAM); |
- rtc::scoped_ptr<Socket> mtu_socket(socket); |
- if (socket == NULL) { |
- LOG_F(LS_WARNING) << "Couldn't create socket while estimating MTU."; |
- } else { |
- if (mtu_socket->Connect(candidate.address()) < 0 || |
- mtu_socket->EstimateMTU(&mtu) < 0) { |
- LOG_F(LS_WARNING) << "Failed to estimate MTU, error=" |
- << mtu_socket->GetError(); |
- } |
- } |
- |
- LOG_F(LS_VERBOSE) << "Using MTU of " << mtu << " bytes"; |
- tcp_->NotifyMTU(mtu); |
- AdjustClock(); |
-} |
- |
-void PseudoTcpChannel::OnTcpOpen(PseudoTcp* tcp) { |
- LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]"; |
- ASSERT(cs_.CurrentThreadIsOwner()); |
- ASSERT(worker_thread_->IsCurrent()); |
- ASSERT(tcp == tcp_); |
- if (stream_) { |
- stream_readable_ = true; |
- pending_read_event_ = true; |
- stream_thread_->Post(this, MSG_ST_EVENT, |
- new EventData(SE_OPEN | SE_READ | SE_WRITE)); |
- } |
-} |
- |
-void PseudoTcpChannel::OnTcpReadable(PseudoTcp* tcp) { |
- //LOG_F(LS_VERBOSE); |
- ASSERT(cs_.CurrentThreadIsOwner()); |
- ASSERT(worker_thread_->IsCurrent()); |
- ASSERT(tcp == tcp_); |
- if (stream_) { |
- stream_readable_ = true; |
- if (!pending_read_event_) { |
- pending_read_event_ = true; |
- stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ)); |
- } |
- } |
-} |
- |
-void PseudoTcpChannel::OnTcpWriteable(PseudoTcp* tcp) { |
- //LOG_F(LS_VERBOSE); |
- ASSERT(cs_.CurrentThreadIsOwner()); |
- ASSERT(worker_thread_->IsCurrent()); |
- ASSERT(tcp == tcp_); |
- if (stream_) |
- stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_WRITE)); |
-} |
- |
-void PseudoTcpChannel::OnTcpClosed(PseudoTcp* tcp, uint32 nError) { |
- LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]"; |
- ASSERT(cs_.CurrentThreadIsOwner()); |
- ASSERT(worker_thread_->IsCurrent()); |
- ASSERT(tcp == tcp_); |
- if (stream_) |
- stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, nError)); |
-} |
- |
-// |
-// Multi-thread methods |
-// |
- |
-void PseudoTcpChannel::OnMessage(Message* pmsg) { |
- if (pmsg->message_id == MSG_WK_CLOCK) { |
- |
- ASSERT(worker_thread_->IsCurrent()); |
- //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_WK_CLOCK)"; |
- CritScope lock(&cs_); |
- if (tcp_) { |
- tcp_->NotifyClock(PseudoTcp::Now()); |
- AdjustClock(false); |
- } |
- |
- } else if (pmsg->message_id == MSG_WK_PURGE) { |
- |
- ASSERT(worker_thread_->IsCurrent()); |
- LOG_F(LS_INFO) << "(MSG_WK_PURGE)"; |
- // At this point, we know there are no additional worker thread messages. |
- CritScope lock(&cs_); |
- ASSERT(NULL == session_); |
- ASSERT(NULL == channel_); |
- worker_thread_ = NULL; |
- CheckDestroy(); |
- |
- } else if (pmsg->message_id == MSG_ST_EVENT) { |
- |
- ASSERT(stream_thread_->IsCurrent()); |
- //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_ST_EVENT, " |
- // << data->event << ", " << data->error << ")"; |
- ASSERT(stream_ != NULL); |
- EventData* data = static_cast<EventData*>(pmsg->pdata); |
- if (data->event & SE_READ) { |
- CritScope lock(&cs_); |
- pending_read_event_ = false; |
- } |
- stream_->SignalEvent(stream_, data->event, data->error); |
- delete data; |
- |
- } else if (pmsg->message_id == MSG_SI_DESTROYCHANNEL) { |
- |
- ASSERT(signal_thread_->IsCurrent()); |
- LOG_F(LS_INFO) << "(MSG_SI_DESTROYCHANNEL)"; |
- ASSERT(session_ != NULL); |
- ASSERT(channel_ != NULL); |
- session_->DestroyChannel(content_name_, channel_->component()); |
- |
- } else if (pmsg->message_id == MSG_SI_DESTROY) { |
- |
- ASSERT(signal_thread_->IsCurrent()); |
- LOG_F(LS_INFO) << "(MSG_SI_DESTROY)"; |
- // The message queue is empty, so it is safe to destroy ourselves. |
- delete this; |
- |
- } else { |
- ASSERT(false); |
- } |
-} |
- |
-IPseudoTcpNotify::WriteResult PseudoTcpChannel::TcpWritePacket( |
- PseudoTcp* tcp, const char* buffer, size_t len) { |
- ASSERT(cs_.CurrentThreadIsOwner()); |
- ASSERT(tcp == tcp_); |
- ASSERT(NULL != channel_); |
- rtc::PacketOptions packet_options; |
- int sent = channel_->SendPacket(buffer, len, packet_options); |
- if (sent > 0) { |
- //LOG_F(LS_VERBOSE) << "(" << sent << ") Sent"; |
- return IPseudoTcpNotify::WR_SUCCESS; |
- } else if (IsBlockingError(channel_->GetError())) { |
- LOG_F(LS_VERBOSE) << "Blocking"; |
- return IPseudoTcpNotify::WR_SUCCESS; |
- } else if (channel_->GetError() == EMSGSIZE) { |
- LOG_F(LS_ERROR) << "EMSGSIZE"; |
- return IPseudoTcpNotify::WR_TOO_LARGE; |
- } else { |
- PLOG(LS_ERROR, channel_->GetError()) << "PseudoTcpChannel::TcpWritePacket"; |
- ASSERT(false); |
- return IPseudoTcpNotify::WR_FAIL; |
- } |
-} |
- |
-void PseudoTcpChannel::AdjustClock(bool clear) { |
- ASSERT(cs_.CurrentThreadIsOwner()); |
- ASSERT(NULL != tcp_); |
- |
- long timeout = 0; |
- if (tcp_->GetNextClock(PseudoTcp::Now(), timeout)) { |
- ASSERT(NULL != channel_); |
- // Reset the next clock, by clearing the old and setting a new one. |
- if (clear) |
- worker_thread_->Clear(this, MSG_WK_CLOCK); |
- worker_thread_->PostDelayed(std::max(timeout, 0L), this, MSG_WK_CLOCK); |
- return; |
- } |
- |
- delete tcp_; |
- tcp_ = NULL; |
- ready_to_connect_ = false; |
- |
- if (channel_) { |
- // If TCP has failed, no need for channel_ anymore |
- signal_thread_->Post(this, MSG_SI_DESTROYCHANNEL); |
- } |
-} |
- |
-void PseudoTcpChannel::CheckDestroy() { |
- ASSERT(cs_.CurrentThreadIsOwner()); |
- if ((worker_thread_ != NULL) || (stream_ != NULL)) |
- return; |
- signal_thread_->Post(this, MSG_SI_DESTROY); |
-} |
- |
-/////////////////////////////////////////////////////////////////////////////// |
-// PseudoTcpChannel::InternalStream |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-PseudoTcpChannel::InternalStream::InternalStream(PseudoTcpChannel* parent) |
- : parent_(parent) { |
-} |
- |
-PseudoTcpChannel::InternalStream::~InternalStream() { |
- Close(); |
-} |
- |
-StreamState PseudoTcpChannel::InternalStream::GetState() const { |
- if (!parent_) |
- return SS_CLOSED; |
- return parent_->GetState(); |
-} |
- |
-StreamResult PseudoTcpChannel::InternalStream::Read( |
- void* buffer, size_t buffer_len, size_t* read, int* error) { |
- if (!parent_) { |
- if (error) |
- *error = ENOTCONN; |
- return SR_ERROR; |
- } |
- return parent_->Read(buffer, buffer_len, read, error); |
-} |
- |
-StreamResult PseudoTcpChannel::InternalStream::Write( |
- const void* data, size_t data_len, size_t* written, int* error) { |
- if (!parent_) { |
- if (error) |
- *error = ENOTCONN; |
- return SR_ERROR; |
- } |
- return parent_->Write(data, data_len, written, error); |
-} |
- |
-void PseudoTcpChannel::InternalStream::Close() { |
- if (!parent_) |
- return; |
- parent_->Close(); |
- parent_ = NULL; |
-} |
- |
-/////////////////////////////////////////////////////////////////////////////// |
- |
-} // namespace cricket |