123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- /**
- * Tencent is pleased to support the open source community by making Tars available.
- *
- * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved.
- *
- * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * https://opensource.org/licenses/BSD-3-Clause
- *
- * Unless required by applicable law or agreed to in writing, software distributed
- * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
- * CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
- #include "util/tc_thread_pool.h"
- #include "util/tc_timeprovider.h"
- #include "servant/ServantHandle.h"
- #include "servant/Application.h"
- #include "servant/ServantHelper.h"
- #include "servant/AppProtocol.h"
- #include "servant/BaseF.h"
- #include "servant/KeepAliveNodeF.h"
- #include "servant/Cookie.h"
- #include "servant/Application.h"
- // #ifdef TARS_OPENTRACKING
- // #include "servant/text_map_carrier.h"
- // #endif
- namespace tars
- {
- /////////////////////////////////////////////////////////////////////////
- //
- ServantHandle::ServantHandle(Application *application)
- : _application(application)
- {
-
- }
- ServantHandle::~ServantHandle()
- {
- auto it = _servants.begin();
- while(it != _servants.end())
- {
- try
- {
- it->second->destroy();
- }
- catch(exception &ex)
- {
- TLOGERROR("[ServantHandle::destroy error:" << ex.what() << "]" << endl);
- }
- catch(...)
- {
- TLOGERROR("[ServantHandle::destroy unknown exception error]" << endl);
- }
- ++it;
- }
- }
- void ServantHandle::handleAsyncResponse()
- {
- ReqMessagePtr resp;
- auto it = _servants.begin();
- while (it != _servants.end())
- {
- while (it->second->getResponseQueue().pop_front(resp))
- {
- try
- {
- if (resp->response->iRet == TARSSERVERSUCCESS)
- {
- it->second->doResponse(resp);
- }
- else if (resp->pObjectProxy == NULL)
- {
- it->second->doResponseNoRequest(resp);
- }
- else
- {
- it->second->doResponseException(resp);
- }
- }
- catch (exception& e)
- {
- TLOGERROR("[ServantHandle::doResponse ex:" << e.what() << "]" << endl);
- }
- catch (...)
- {
- TLOGERROR("[ServantHandle::doResponse error]" << endl);
- }
- }
- //业务处理附加的自有消息
- try
- {
- it->second->doCustomMessage(false);
- it->second->doCustomMessage();
- }
- catch (exception& e)
- {
- TLOGERROR("[ServantHandle::doCustemMessage ex:" << e.what() << "]" << endl);
- }
- catch (...)
- {
- TLOGERROR("[ServantHandle::doCustemMessage ex.]" << endl);
- }
- ++it;
- }
- }
- void ServantHandle::handleCustomMessage(bool bExpectIdle)
- {
- for (auto it = _servants.begin(); it != _servants.end(); it++)
- {
- //业务处理附加的自有消息
- try
- {
- it->second->doCustomMessage(bExpectIdle);
- it->second->doCustomMessage();
- }
- catch (exception& e)
- {
- TLOGERROR("[ServantHandle::doCustemMessage ex:" << e.what() << "]" << endl);
- }
- catch (...)
- {
- TLOGERROR("[ServantHandle::doCustemMessage ex.]" << endl);
- }
- }
- }
- bool ServantHandle::allFilterIsEmpty()
- {
- auto it = _servants.begin();
- while (it != _servants.end())
- {
- if (!it->second->getResponseQueue().empty())
- {
- return false;
- }
- ++it;
- }
- return true;
- }
- void ServantHandle::initialize()
- {
- if(TC_CoroutineScheduler::scheduler())
- {
- ServantProxyThreadData::getData()->_sched = TC_CoroutineScheduler::scheduler();
- }
- ServantPtr servant = _application->getServantHelper()->create(_bindAdapter->getName());
- if (servant)
- {
- _servants[servant->getName()] = servant;
- }
- else
- {
- TLOGERROR("[ServantHandle initialize createServant ret null, for adapter `" +_bindAdapter->getName() + "`]" << endl);
- cerr << "ServantHandle initialize createServant ret null, for adapter `" +_bindAdapter->getName() + "`]" << endl;
- RemoteNotify::getInstance()->report("initialize createServant error: no adapter:" + _bindAdapter->getName());
- TC_Common::msleep(100);
- exit(-1);
- }
- auto it = _servants.begin();
- if(it == _servants.end())
- {
- TLOGERROR("[initialize error: no servant exists]" << endl);
- RemoteNotify::getInstance()->report("initialize error: no servant exists.");
- TC_Common::msleep(100);
- exit(-1);
- }
- while(it != _servants.end())
- {
- try
- {
- it->second->setHandle(this);
- it->second->initialize();
- TLOGTARS("[" << it->second->getName() << " initialize]" << endl);
- }
- catch(exception &ex)
- {
- TLOGERROR("[initialize error:" << ex.what() << "]" << endl);
- RemoteNotify::getInstance()->report("initialize error:" + string(ex.what()));
- TC_Common::msleep(100);
- exit(-1);
- }
- catch(...)
- {
- TLOGERROR("[initialize unknown exception error]" << endl);
- RemoteNotify::getInstance()->report("initialize unknown exception error");
- TC_Common::msleep(100);
- exit(-1);
- }
- ++it;
- }
- }
- void ServantHandle::heartbeat()
- {
- time_t fcur = TNOW;
- if (abs(fcur - _bindAdapter->getHeartBeatTime()) > HEART_BEAT_INTERVAL)
- {
- _bindAdapter->setHeartBeatTime(fcur);
- TARS_KEEPALIVE(_bindAdapter->getName());
- //上报连接数 比率
- if (_bindAdapter->_pReportConRate)
- {
- _bindAdapter->_pReportConRate->report((int)(_bindAdapter->getNowConnection() * 1000 / _bindAdapter->getMaxConns()));
- }
- //有队列, 且队列长度>0才上报
- if (_bindAdapter->_pReportQueue)
- {
- _bindAdapter->_pReportQueue->report((int)_bindAdapter->getRecvBufferSize());
- }
- }
- }
- CurrentPtr ServantHandle::createCurrent(const shared_ptr<TC_EpollServer::RecvContext> &data)
- {
- CurrentPtr current = new Current(this);
- try
- {
- current->initialize(data);
- }
- catch (TarsDecodeException &ex)
- {
- TLOGERROR("[ServantHandle::handle request protocol decode error:" << ex.what() << "]" << endl);
- close(data);
- return NULL;
- }
- //只有TARS协议才处理
- if(current->getBindAdapter()->isTarsProtocol())
- {
- int64_t now = TNOWMS;
- //数据在队列中的时间超过了客户端等待的时间(TARS协议)
- if (current->_request.iTimeout > 0 && (now - data->recvTimeStamp()) > current->_request.iTimeout)
- {
- //上报超时数目
- if (data->adapter()->_pReportTimeoutNum)
- data->adapter()->_pReportTimeoutNum->report(1);
- TLOGERROR("[TARS][ServantHandle::handle queue timeout:"
- << current->_request.sServantName << ", func:"
- << current->_request.sFuncName << ", recv time:"
- << data->recvTimeStamp() << ", queue timeout:"
- << data->adapter()->getQueueTimeout() << ", timeout:"
- << current->_request.iTimeout << ", now:"
- << now << ", ip:" << data->ip() << ", port:" << data->port() << "]" << endl);
- current->sendResponse(TARSSERVERQUEUETIMEOUT);
- return NULL;
- }
- }
- return current;
- }
- CurrentPtr ServantHandle::createCloseCurrent(const shared_ptr<TC_EpollServer::RecvContext> &data)
- {
- CurrentPtr current = new Current(this);
- current->initializeClose(data);
- current->setReportStat(false);
- current->setCloseType(data->closeType());
- return current;
- }
- void ServantHandle::handleClose(const shared_ptr<TC_EpollServer::RecvContext> &data)
- {
- TLOGTARS("[ServantHandle::handleClose,adapter:" << data->adapter()->getName() << ",peer:" << data->ip() << ":" << data->port() << "]"<< endl);
- CurrentPtr current = createCloseCurrent(data);
- auto sit = _servants.find(current->getServantName());
- if (sit == _servants.end())
- {
- TLOGERROR("[TARS]ServantHandle::handleClose,adapter:" << data->adapter()->getName() << ",peer:" << data->ip() << ":" << data->port() << ", " << current->getServantName() << " not found" << endl);
- return;
- }
- try
- {
- //业务逻辑处理
- sit->second->doClose(current);
- }
- catch (exception& ex)
- {
- TLOGERROR("[TARS]ServantHandle::handleClose " << ex.what() << endl);
- return;
- }
- catch (...)
- {
- TLOGERROR("[TARS]ServantHandle::handleClose unknown error" << endl);
- return;
- }
- }
- void ServantHandle::handleTimeout(const shared_ptr<TC_EpollServer::RecvContext> &data)
- {
- CurrentPtr current = createCurrent(data);
- if (!current) return;
- //上报超时数目
- if(data->adapter()->_pReportTimeoutNum)
- data->adapter()->_pReportTimeoutNum->report(1);
- TLOGERROR("[ServantHandle::handleTimeout adapter '"
- << data->adapter()->getName()
- << "', recvtime:" << data->recvTimeStamp() << "|"
- << ", timeout:" << data->adapter()->getQueueTimeout()
- << ", id:" << current->getRequestId() << "]" << endl);
- if (current->getBindAdapter()->isTarsProtocol())
- {
- current->sendResponse(TARSSERVERQUEUETIMEOUT);
- }
- }
- void ServantHandle::handleOverload(const shared_ptr<TC_EpollServer::RecvContext> &data)
- {
- CurrentPtr current = createCurrent(data);
- if (!current) return;
- TLOGERROR("[ServantHandle::handleOverload adapter '"
- << data->adapter()->getName()
- << "',overload:-1,queue capacity:"
- << data->adapter()->getQueueCapacity()
- << ",id:" << current->getRequestId() << "]" << endl);
- if (current->getBindAdapter()->isTarsProtocol())
- {
- current->sendResponse(TARSSERVEROVERLOAD);
- }
- }
- void ServantHandle::handle(const shared_ptr<TC_EpollServer::RecvContext> &data)
- {
- CurrentPtr current = createCurrent(data);
- if (!current) return;
- if (current->getBindAdapter()->isTarsProtocol())
- {
- handleTarsProtocol(current);
- }
- else
- {
- handleNoTarsProtocol(current);
- }
- }
- // #ifdef TARS_OPENTRACKING
- // void ServantHandle::processTracking(const TarsCurrentPtr ¤t)
- // {
- // if(!(Application::getCommunicator()->_traceManager))
- // {
- // return;
- // }
- // ServantProxyThreadData * sptd = ServantProxyThreadData::getData();
- // assert(sptd);
- // if(!sptd)
- // {
- // return;
- // }
- // //提取packet中的span信息,更新为被调的span信息后设置到sptd->_trackInfoMap;
- // sptd->_trackInfoMap.clear();
-
- // if (IS_MSG_TYPE(current->getMessageType(), tars::TARSMESSAGETYPETRACK))
- // {
- // map<string, string>::const_iterator trackinfoIter = current->getRequestStatus().find(ServantProxy::STATUS_TRACK_KEY);
- // TLOGTARS("[TARS] servant got a tracking request, message_type set" << current->getMessageType() << endl);
- // if (trackinfoIter != current->getRequestStatus().end())
- // {
- // TLOGTARS("[TARS] servant got a tracking request, tracking key:" << trackinfoIter->second << endl);
- // string context = trackinfoIter->second;
- // char szBuffer[context.size() + 1];
- // memset(szBuffer, 0x00, context.size() + 1);
- // memcpy(szBuffer, context.c_str(), context.size());
-
- // std::unordered_map<std::string, std::string> text_map;
- // write_span_context(text_map, szBuffer);
- // TextMapCarrier carrier(text_map);
- // auto tracer = Application::getCommunicator()->_traceManager->_tracer;
- // auto span_context_maybe = tracer->Extract(carrier);
- // if(!span_context_maybe)
- // {
- // //error
- // TLOGERROR("[TARS] servant got a tracking request, but extract the span context fail");
- // return ;
- // }
- // string funcName = current->getFuncName();
- // auto child_span = tracer->StartSpan(funcName, {opentracing::ChildOf(span_context_maybe->get())});
-
- // //text_map.clear();
- // auto err = tracer->Inject(child_span->context(), carrier);
- // assert(err);
- // sptd->_trackInfoMap = text_map;
- // _spanMap[current->getRequestId()].reset(child_span.release());
- // return ;
- // }
- // }
- // return ;
- // }
- // void ServantHandle::finishTracking(int ret, const TarsCurrentPtr ¤t)
- // {
- // int requestId = current->getRequestId();
-
- // if(_spanMap.find(requestId) != _spanMap.end())
- // {
- // auto spanIter = _spanMap.find(requestId);
- // spanIter->second->SetTag("Retcode", ret);
- // spanIter->second->Finish();
- // _spanMap.erase(requestId);
- // }
- // }
- // #endif
- bool ServantHandle::processDye(const CurrentPtr ¤t, string& dyeingKey)
- {
- //当前线程的线程数据
- ServantProxyThreadData *sptd = ServantProxyThreadData::getData();
- if (sptd)
- {
- sptd->_data._dyeingKey = "";
- }
- //当前请求已经被染色, 需要打印染色日志
- map<string, string>::const_iterator dyeingIt = current->getRequestStatus().find(ServantProxy::STATUS_DYED_KEY);
- if (IS_MSG_TYPE(current->getMessageType(), tars::TARSMESSAGETYPEDYED))
- {
- TLOGTARS("[servant got a dyeing request, message_type set: " << current->getMessageType() << "]" << endl);
- if (dyeingIt != current->getRequestStatus().end())
- {
- TLOGTARS("[servant got a dyeing request, dyeing key: " << dyeingIt->second << "]" << endl);
- dyeingKey = dyeingIt->second;
- }
- return true;
- }
- //servant已经被染色, 开启染色日志
- if (_application->getServantHelper()->isDyeing())
- {
- map<string, string>::const_iterator dyeingKeyIt = current->getRequestStatus().find(ServantProxy::STATUS_GRID_KEY);
- if (dyeingKeyIt != current->getRequestStatus().end() &&
- _application->getServantHelper()->isDyeingReq(dyeingKeyIt->second, current->getServantName(), current->getFuncName()))
- {
- TLOGTARS("[TARS] dyeing servant got a dyeing req, key:" << dyeingKeyIt->second << endl);
- dyeingKey = dyeingKeyIt->second;
- return true;
- }
- }
- return false;
- }
- bool ServantHandle::processTrace(const CurrentPtr ¤t)
- {
- //当前线程的线程数据
- ServantProxyThreadData* sptd = ServantProxyThreadData::getData();
- if (sptd)
- {
- sptd->_traceCall = false;
- sptd->_traceContext.reset();
- }
- // 如果调用链需要追踪,需要初始化线程私有追踪参数
- map<string, string>::const_iterator traceIt = current->getRequestStatus().find(ServantProxy::STATUS_TRACE_KEY);
- if (IS_MSG_TYPE(current->getMessageType(), tars::TARSMESSAGETYPETRACE))
- {
- TLOGTARS("[TARS] servant got a trace request, message_type set " << current->getMessageType() << endl);
- if (traceIt != current->getRequestStatus().end())
- {
- TLOGTARS("[TARS] servant got a trace request, trace key:" << traceIt->second << endl);
- if (sptd->initTrace(traceIt->second))
- {
- sptd->_traceCall = true;
- return true;
- }
- else
- {
- TLOGTARS("[TARS] servant got a trace request, but trace key is error:" << traceIt->second << endl);
- }
- }
- }
- return false;
- }
- bool ServantHandle::processCookie(const CurrentPtr ¤t, map<string, string> &cookie)
- {
- const static string STATUS = "STATUS_";
- std::for_each(current->getRequestStatus().begin(), current->getRequestStatus().end(),[&](const map<string, string>::value_type& p){
- if(p.first.size() > STATUS.size() && TC_Port::strncasecmp(p.first.c_str(), STATUS.c_str(), STATUS.size()) == 0) {
- return;
- }
- cookie.insert(make_pair(p.first, p.second));
- });
- return !cookie.empty();
- }
- bool ServantHandle::checkValidSetInvoke(const CurrentPtr ¤t)
- {
- /*是否允许检查合法性*/
- if (ServerConfig::IsCheckSet == 0)
- {
- //不检查
- return true;
- }
- bool isSetInvoke = IS_MSG_TYPE(current->getMessageType(), tars::TARSMESSAGETYPESETNAME);
- //客户端按set规则调用且服务端启用set
- if (isSetInvoke && ClientConfig::SetOpen)
- {
- /**
- * 合法性规则:
- * 1 客户端set名称与服务端set在同一分组,eg, test.s.1 <-> test.s.1
- * 2 客户端set名称与服务端set在同一地区,eg, test.s.* <-> test.s.1 | test.s.2 | test.s.*
- * 3 客户端set名称与服务端set属于不同名称,eg,test1.s.1 <->test2.n.2
- * 4 1,2,3条件都不满足,则认为该调用不合法
- */
- map<string, string>::const_iterator setIt = current->getRequestStatus().find(ServantProxy::STATUS_SETNAME_VALUE);
- string sSetName("");
- if (setIt != current->getRequestStatus().end())
- {
- TLOGTARS("[servant got a setname request, setname key:" << setIt->second << "]" << endl);
- sSetName = setIt->second;
- if (ClientConfig::SetDivision == sSetName)
- {
- return true;
- }
- else
- {
- //属于同一地区是也属于合法调用
- string setArea1 = ClientConfig::SetDivision.substr(0,ClientConfig::SetDivision.find_last_of("."));
- string setArea2 = sSetName.substr(0,sSetName.find_last_of("."));
- if (setArea1 == setArea2)
- {
- return true;
- }
- else if (ClientConfig::SetDivision.substr(0,ClientConfig::SetDivision.find_first_of(".")) !=
- sSetName.substr(0,sSetName.find_first_of(".")))
- {
- //属于不同的set之间调用也属于合法
- return true;
- }
- else
- {
- TLOGERROR("[ServantHandle::checkValidSetInvoke|"
- << current->getIp() << "|"
- << current->getMessageType() << "|"
- << current->getServantName() << "|"
- << current->getFuncName() << "|client:"
- << ClientConfig::SetDivision << "|server:"
- << sSetName << "]" << endl);
- current->sendResponse(TARSINVOKEBYINVALIDESET);
- return false;
- }
- }
- }
- else
- {
- TLOGERROR("[ServantHandle::checkValidSetInvoke|"
- << current->getIp() << "|"
- << current->getMessageType() << "|"
- << current->getServantName() << "|"
- << current->getFuncName() << "|client:"
- << ClientConfig::SetDivision << "|server:"
- << sSetName << "]" << endl);
- current->sendResponse(TARSINVOKEBYINVALIDESET);
- return false;
- }
- }
- //没有按set规则调用
- return true;
- }
- void ServantHandle::handleTarsProtocol(const CurrentPtr ¤t)
- {
- TLOGTARS("[ServantHandle::handleTarsProtocol current:"
- << current->getIp() << "|"
- << current->getPort() << "|"
- << current->getMessageType() << "|"
- << current->getServantName() << "|"
- << current->getFuncName() << "|"
- << current->getRequestId() << "|"
- << TC_Common::tostr(current->getRequestStatus()) << "]"<<endl);
- //检查set调用合法性
- if (!checkValidSetInvoke(current))
- {
- return;
- }
- //处理染色消息
- string dyeingKey = "";
- TarsDyeingSwitch dyeSwitch;
- if (processDye(current, dyeingKey))
- {
- dyeSwitch.enableDyeing(dyeingKey);
- }
- processTrace(current);
- //处理cookie
- map<string, string> cookie;
- CookieOp cookieOp;
- if (processCookie(current, cookie))
- {
- cookieOp.setCookie(cookie);
- current->setCookie(cookie);
- }
- // processSample(current);
- auto sit = _servants.find(current->getServantName());
- if (sit == _servants.end())
- {
- current->sendResponse(TARSSERVERNOSERVANTERR);
- // #ifdef TARS_OPENTRACKING
- // finishTracking(TARSSERVERNOSERVANTERR, current);
- // #endif
- return;
- }
- int ret = TARSSERVERUNKNOWNERR;
- string sResultDesc = "";
- ResponsePacket response;
- try
- {
- //业务逻辑处理
- ret = sit->second->dispatch(current, response.sBuffer);
- }
- catch(TarsDecodeException &ex)
- {
- TLOGERROR("[ServantHandle::handleTarsProtocol " << ex.what() << "]" << endl);
- ret = TARSSERVERDECODEERR;
- sResultDesc = ex.what();
- }
- catch(TarsEncodeException &ex)
- {
- TLOGERROR("[ServantHandle::handleTarsProtocol " << ex.what() << "]" << endl);
- ret = TARSSERVERENCODEERR;
- sResultDesc = ex.what();
- }
- catch(exception &ex)
- {
- TLOGERROR("[ServantHandle::handleTarsProtocol " << ex.what() << "]" << endl);
- ret = TARSSERVERUNKNOWNERR;
- sResultDesc = ex.what();
- }
- catch(...)
- {
- TLOGERROR("[ServantHandle::handleTarsProtocol unknown error]" << endl);
- ret = TARSSERVERUNKNOWNERR;
- sResultDesc = "handleTarsProtocol unknown exception error";
- }
- if(ret == TARSSERVERNOFUNCERR)
- {
- ret = sit->second->doNoFunc(current);
- }
-
- //单向调用或者业务不需要同步返回
- if (current->isResponse())
- {
- current->sendResponse(ret, response, Current::TARS_STATUS(), sResultDesc);
- }
- #ifdef TARS_OPENTRACKING
- finishTracking(ret, current);
- #endif
- }
- void ServantHandle::handleNoTarsProtocol(const TarsCurrentPtr ¤t)
- {
- TLOGTARS("[ServantHandle::handleNoTarsProtocol current:"
- << current->getIp() << "|"
- << current->getPort() << "|"
- << current->getServantName() << "]" << endl);
- auto sit = _servants.find(current->getServantName());
- assert(sit != _servants.end());
- vector<char> buffer;
- try
- {
- //业务逻辑处理
- sit->second->dispatch(current, buffer);
- }
- catch(exception &ex)
- {
- TLOGERROR("[ServantHandle::handleNoTarsProtocol " << ex.what() << "]" << endl);
- }
- catch(...)
- {
- TLOGERROR("[ServantHandle::handleNoTarsProtocol unknown error]" << endl);
- }
- if (current->isResponse() && !buffer.empty())
- {
- current->sendResponse((const char *)(buffer.data()), (uint32_t)buffer.size());
- }
- }
- ////////////////////////////////////////////////////////////////////////////
- }
|