From 680934812598d2c9116303f3245e7a9d60ff58bf Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Tue, 3 Dec 2013 17:06:06 -0800 Subject: Creating a cleaner branch --- indra/win_crash_logger/llcrashloggerwindows.cpp | 178 +++++++++++++++++++++++- 1 file changed, 175 insertions(+), 3 deletions(-) mode change 100755 => 100644 indra/win_crash_logger/llcrashloggerwindows.cpp (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp old mode 100755 new mode 100644 index 36d988ead7..49c7ade135 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -42,6 +42,10 @@ #include "lldxhardware.h" #include "lldir.h" #include "llsdserialize.h" +#include "llsdutil.h" + +#include +#include #define MAX_LOADSTRING 100 #define MAX_STRING 2048 @@ -64,6 +68,7 @@ BOOL gFirstDialog = TRUE; // Are we currently handling the Send/Don't Send dialo std::stringstream gDXInfo; bool gSendLogs = false; +LLCrashLoggerWindows* LLCrashLoggerWindows::sInstance = NULL; //Conversion from char* to wchar* //Replacement for ATL macros, doesn't allocate memory @@ -240,14 +245,181 @@ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam LLCrashLoggerWindows::LLCrashLoggerWindows(void) { + if (LLCrashLoggerWindows::sInstance==NULL) + { + sInstance = this; + } } LLCrashLoggerWindows::~LLCrashLoggerWindows(void) { + sInstance = NULL; +} + +bool LLCrashLoggerWindows::getMessageWithTimeout(MSG *msg, UINT to) +{ + bool res; + const int timerID=37; + SetTimer(NULL, timerID, to, NULL); + res = GetMessage(msg, NULL, 0, 0); + KillTimer(NULL, timerID); + if (!res) + return false; + if (msg->message == WM_TIMER && msg->hwnd == NULL && msg->wParam == 1) + return false; //TIMEOUT! You could call SetLastError() or something... + return true; +} + +int LLCrashLoggerWindows::processingLoop() { + const int millisecs=1000; + static int first_connect = 1; + + LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE ); + + MSG msg; + + bool result; + + while (1) + { + result = getMessageWithTimeout(&msg, millisecs); + if ( result ) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (first_connect ) + { + if ( mClientsConnected > 0) + { + first_connect = 0; + } + } + else + { + if (mClientsConnected == 0) + { + break; + } + if (!mKeyMaster.isProcessAlive(mPID, mProcName) ) + { + break; + } + } + } + + llinfos << "session ending.." << llendl; + + llinfos << "clients connected :" << mClientsConnected << llendl; + + return 0; +} + + +void LLCrashLoggerWindows::OnClientConnected(void* context, + const google_breakpad::ClientInfo* client_info) +{ + llinfos << "client start. pid = " << client_info->pid() << llendl; + sInstance->mClientsConnected++; +} + +void LLCrashLoggerWindows::OnClientExited(void* context, + const google_breakpad::ClientInfo* client_info) +{ + llinfos << "client end. pid = " << client_info->pid() << llendl; + sInstance->mClientsConnected--; +} + +/* +void LLCrashLoggerWindows::OnClientDumpRequest(void* context, + const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path) +{ + ProcessingLock lock; + + if (!file_path) + { + llwarns << "dump with no file path" << llendl; + return; + } + if (!client_info) + { + llwarns << "dump with no client info" << llendl; + return; + } + + LLCrashLoggerWindows* self = static_cast(context); + if (!self) + { + llwarns << "dump with no context" << llendl; + return; + } + + DWORD pid = client_info->pid(); + + +// Send the crash dump using a worker thread. This operation has retry +// logic in case there is no internet connection at the time. +DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map, +dump_location.value()); +if (!::QueueUserWorkItem(&CrashService::AsyncSendDump, +dump_job, WT_EXECUTELONGFUNCTION)) { +LOG(ERROR) << "could not queue job"; +} +} +*/ + +bool LLCrashLoggerWindows::initCrashServer() +{ + //For Breakpad on Windows we need a full Out of Process service to get good data. + //This routine starts up the service on a named pipe that the viewer will then + //communicate with. + using namespace google_breakpad; + + LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE ); + std::string dump_path = options["dumpdir"].asString(); + mClientsConnected = 0; + mPID = options["pid"].asInteger(); + mProcName = options["procname"].asString(); + + std::wostringstream ws; + //Generate a quasi-uniq name for the named pipe. For our purposes + //this is unique-enough with least hassle. Worst case for duplicate name + //is a second instance of the viewer will not do crash reporting. + ws << mCrashReportPipeStr << mPID; + std::wstring wpipe_name = ws.str(); + + std::wstring wdump_path; + wdump_path.assign(dump_path.begin(), dump_path.end()); + + //Pipe naming conventions: http://msdn.microsoft.com/en-us/library/aa365783%28v=vs.85%29.aspx + mCrashHandler = new CrashGenerationServer( (WCHAR *)wpipe_name.c_str(), + NULL, + &LLCrashLoggerWindows::OnClientConnected, this, + NULL, NULL, // &LLCrashLoggerWindows::OnClientDumpRequest, this, + &LLCrashLoggerWindows::OnClientExited, this, + NULL, NULL, + true, &wdump_path); + + if (!mCrashHandler) { + //Failed to start the crash server. + llwarns << "Failed to init crash server." << llendl; + return false; + } + + // Start servicing clients. + if (!mCrashHandler->Start()) { + llwarns << "Failed to start crash server." << llendl; + return false; + } + + return true; } bool LLCrashLoggerWindows::init(void) { + initCrashServer(); bool ok = LLCrashLogger::init(); if(!ok) return false; @@ -291,18 +463,16 @@ void LLCrashLoggerWindows::gatherPlatformSpecificFiles() SetCursor(gCursorWait); // At this point we're responsive enough the user could click the close button SetCursor(gCursorArrow); - mDebugLog["DisplayDeviceInfo"] = gDXHardware.getDisplayInfo(); + //mDebugLog["DisplayDeviceInfo"] = gDXHardware.getDisplayInfo(); //Not initialized. } bool LLCrashLoggerWindows::mainLoop() { llinfos << "CrashSubmitBehavior is " << mCrashBehavior << llendl; - // Note: parent hwnd is 0 (the desktop). No dlg proc. See Petzold (5th ed) HexCalc example, Chapter 11, p529 // win_crash_logger.rc has been edited by hand. // Dialogs defined with CLASS "WIN_CRASH_LOGGER" (must be same as szWindowClass) gProductName = mProductName; - gHwndProgress = CreateDialog(hInst, MAKEINTRESOURCE(IDD_PROGRESS), 0, NULL); ProcessCaption(gHwndProgress); ShowWindow(gHwndProgress, SW_HIDE ); @@ -371,5 +541,7 @@ bool LLCrashLoggerWindows::cleanup() } PostQuitMessage(0); commonCleanup(); + mKeyMaster.releaseMaster(); return true; } + -- cgit v1.2.3 From ea7e6a5174f1bdfc51ada864736d354706534d8b Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Tue, 14 Jan 2014 15:28:35 -0800 Subject: Some cleanup of string to wstring conversion and vice versa. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index 49c7ade135..f0bc03a9c4 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -383,18 +383,17 @@ bool LLCrashLoggerWindows::initCrashServer() mPID = options["pid"].asInteger(); mProcName = options["procname"].asString(); - std::wostringstream ws; //Generate a quasi-uniq name for the named pipe. For our purposes //this is unique-enough with least hassle. Worst case for duplicate name //is a second instance of the viewer will not do crash reporting. - ws << mCrashReportPipeStr << mPID; - std::wstring wpipe_name = ws.str(); + std::wstring wpipe_name; + wpipe_name = mCrashReportPipeStr + stringize(mPID); std::wstring wdump_path; - wdump_path.assign(dump_path.begin(), dump_path.end()); + wdump_path = stringize(dump_path); //Pipe naming conventions: http://msdn.microsoft.com/en-us/library/aa365783%28v=vs.85%29.aspx - mCrashHandler = new CrashGenerationServer( (WCHAR *)wpipe_name.c_str(), + mCrashHandler = new CrashGenerationServer( stringize(wpipe_name).c_str(), NULL, &LLCrashLoggerWindows::OnClientConnected, this, NULL, NULL, // &LLCrashLoggerWindows::OnClientDumpRequest, this, -- cgit v1.2.3 From 00aa2fee6d3841788d7146e5d6d66d0bceff9c3f Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Wed, 15 Jan 2014 21:24:55 -0800 Subject: Fixes from Windows build including utf-16 to utf-8 conversions. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index f0bc03a9c4..03a709d757 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -43,6 +43,7 @@ #include "lldir.h" #include "llsdserialize.h" #include "llsdutil.h" +#include "stringize.h" #include #include @@ -387,13 +388,12 @@ bool LLCrashLoggerWindows::initCrashServer() //this is unique-enough with least hassle. Worst case for duplicate name //is a second instance of the viewer will not do crash reporting. std::wstring wpipe_name; - wpipe_name = mCrashReportPipeStr + stringize(mPID); + wpipe_name = mCrashReportPipeStr + std::wstring(wstringize(mPID)); - std::wstring wdump_path; - wdump_path = stringize(dump_path); + std::wstring wdump_path( wstringize(dump_path) ); //Pipe naming conventions: http://msdn.microsoft.com/en-us/library/aa365783%28v=vs.85%29.aspx - mCrashHandler = new CrashGenerationServer( stringize(wpipe_name).c_str(), + mCrashHandler = new CrashGenerationServer( wpipe_name, NULL, &LLCrashLoggerWindows::OnClientConnected, this, NULL, NULL, // &LLCrashLoggerWindows::OnClientDumpRequest, this, -- cgit v1.2.3 From 33b0ae6ebf8a085a8795a9e5b02455fb7ebf0e6f Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Thu, 23 Jan 2014 17:04:33 -0800 Subject: Debugging changes. fixed broken pipe. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index 03a709d757..30c9cf551b 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -329,6 +329,7 @@ void LLCrashLoggerWindows::OnClientExited(void* context, const google_breakpad::ClientInfo* client_info) { llinfos << "client end. pid = " << client_info->pid() << llendl; + sInstance->mClientsConnected--; } @@ -391,7 +392,7 @@ bool LLCrashLoggerWindows::initCrashServer() wpipe_name = mCrashReportPipeStr + std::wstring(wstringize(mPID)); std::wstring wdump_path( wstringize(dump_path) ); - + //Pipe naming conventions: http://msdn.microsoft.com/en-us/library/aa365783%28v=vs.85%29.aspx mCrashHandler = new CrashGenerationServer( wpipe_name, NULL, -- cgit v1.2.3 From 262f8b84737587fd5c2de38c34ff7a5594cca174 Mon Sep 17 00:00:00 2001 From: obscurestar Date: Sun, 26 Jan 2014 02:56:23 -0800 Subject: Was not using correct name for results of file search. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index 30c9cf551b..cd9b351fdb 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -333,13 +333,11 @@ void LLCrashLoggerWindows::OnClientExited(void* context, sInstance->mClientsConnected--; } -/* + void LLCrashLoggerWindows::OnClientDumpRequest(void* context, const google_breakpad::ClientInfo* client_info, const std::wstring* file_path) { - ProcessingLock lock; - if (!file_path) { llwarns << "dump with no file path" << llendl; @@ -359,18 +357,8 @@ void LLCrashLoggerWindows::OnClientDumpRequest(void* context, } DWORD pid = client_info->pid(); - - -// Send the crash dump using a worker thread. This operation has retry -// logic in case there is no internet connection at the time. -DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map, -dump_location.value()); -if (!::QueueUserWorkItem(&CrashService::AsyncSendDump, -dump_job, WT_EXECUTELONGFUNCTION)) { -LOG(ERROR) << "could not queue job"; } -} -*/ + bool LLCrashLoggerWindows::initCrashServer() { @@ -397,7 +385,7 @@ bool LLCrashLoggerWindows::initCrashServer() mCrashHandler = new CrashGenerationServer( wpipe_name, NULL, &LLCrashLoggerWindows::OnClientConnected, this, - NULL, NULL, // &LLCrashLoggerWindows::OnClientDumpRequest, this, + /*NULL, NULL, */ &LLCrashLoggerWindows::OnClientDumpRequest, this, &LLCrashLoggerWindows::OnClientExited, this, NULL, NULL, true, &wdump_path); -- cgit v1.2.3 From 71b1e7bb700ec886e3a9a01aac08039ed54e532b Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Sun, 26 Jan 2014 10:00:32 -0800 Subject: Warn treated as error kills TS film at 11 --- indra/win_crash_logger/llcrashloggerwindows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index cd9b351fdb..b72f5be853 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -356,7 +356,7 @@ void LLCrashLoggerWindows::OnClientDumpRequest(void* context, return; } - DWORD pid = client_info->pid(); + //DWORD pid = client_info->pid(); } -- cgit v1.2.3 From d2bb4dae980a887a30b206875d8f9419901ed66a Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Fri, 7 Mar 2014 14:58:22 -0800 Subject: Fixes for crash reporter startup race condition, crash reporter CPU use, Secondlife.log filehandle, XP Crash. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 35 +++++++++++++++---------- 1 file changed, 21 insertions(+), 14 deletions(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index b72f5be853..e3356f90ba 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -260,8 +260,7 @@ LLCrashLoggerWindows::~LLCrashLoggerWindows(void) bool LLCrashLoggerWindows::getMessageWithTimeout(MSG *msg, UINT to) { bool res; - const int timerID=37; - SetTimer(NULL, timerID, to, NULL); + UINT_PTR timerID = SetTimer(NULL, NULL, to, NULL); res = GetMessage(msg, NULL, 0, 0); KillTimer(NULL, timerID); if (!res) @@ -273,7 +272,10 @@ bool LLCrashLoggerWindows::getMessageWithTimeout(MSG *msg, UINT to) int LLCrashLoggerWindows::processingLoop() { const int millisecs=1000; - static int first_connect = 1; + int retries = 0; + const int max_retries = 60; + + LL_DEBUGS("CRASHREPORT") << "Entering processing loop for OOP server" << LL_ENDL; LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE ); @@ -290,11 +292,17 @@ int LLCrashLoggerWindows::processingLoop() { DispatchMessage(&msg); } - if (first_connect ) + if ( retries < max_retries ) //Wait up to 1 minute for the viewer to say hello. { - if ( mClientsConnected > 0) + if (mClientsConnected == 0) + { + LL_DEBUGS("CRASHREPORT") << "Waiting for client to connect." << LL_ENDL; + ++retries; + } + else { - first_connect = 0; + LL_INFOS("CRASHREPORT") << "Client has connected!" << LL_ENDL; + retries = max_retries; } } else @@ -311,9 +319,7 @@ int LLCrashLoggerWindows::processingLoop() { } llinfos << "session ending.." << llendl; - - llinfos << "clients connected :" << mClientsConnected << llendl; - + return 0; } @@ -321,16 +327,15 @@ int LLCrashLoggerWindows::processingLoop() { void LLCrashLoggerWindows::OnClientConnected(void* context, const google_breakpad::ClientInfo* client_info) { - llinfos << "client start. pid = " << client_info->pid() << llendl; sInstance->mClientsConnected++; + LL_INFOS("CRASHREPORT") << "Client connected. pid = " << client_info->pid() << " total clients " << sInstance->mClientsConnected << LL_ENDL; } void LLCrashLoggerWindows::OnClientExited(void* context, const google_breakpad::ClientInfo* client_info) { - llinfos << "client end. pid = " << client_info->pid() << llendl; - sInstance->mClientsConnected--; + LL_INFOS("CRASHREPORT") << "Client disconnected. pid = " << client_info->pid() << " total clients " << sInstance->mClientsConnected << LL_ENDL; } @@ -402,19 +407,21 @@ bool LLCrashLoggerWindows::initCrashServer() return false; } + LL_INFOS("CRASHREPORT") << "Initialized OOP server with pipe named " << stringize(wpipe_name) << LL_ENDL; return true; } bool LLCrashLoggerWindows::init(void) { - initCrashServer(); bool ok = LLCrashLogger::init(); if(!ok) return false; + initCrashServer(); + /* mbstowcs( gProductName, mProductName.c_str(), LL_ARRAY_SIZE(gProductName) ); gProductName[ LL_ARRY_SIZE(gProductName) - 1 ] = 0; - swprintf(gProductName, L"Second Life"); + swprintf(gProductName, L"Second Life"); */ llinfos << "Loading dialogs" << llendl; -- cgit v1.2.3 From 0893b49ebdd5c072f099ab7a9aa75d106c0c439b Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Mon, 24 Mar 2014 22:44:51 -0700 Subject: The simple approach has not worked. This hybrid solution should avoid the Windows issues and get us per-run logfiles. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index e3356f90ba..0e683c82bd 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -319,7 +319,11 @@ int LLCrashLoggerWindows::processingLoop() { } llinfos << "session ending.." << llendl; - + + LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE ); + std::string per_run_file = options["dumpdir"].asString() + "SecondLife.log"; + std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log"); + LLFile::copy(per_run_file, log_file); return 0; } -- cgit v1.2.3 From fdb65d2c28b5256d0442a2ab4499d04d91ecc96e Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Tue, 25 Mar 2014 14:05:51 -0700 Subject: Fixed dumb mistake. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index 0e683c82bd..6b5ab111ec 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -320,7 +320,6 @@ int LLCrashLoggerWindows::processingLoop() { llinfos << "session ending.." << llendl; - LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE ); std::string per_run_file = options["dumpdir"].asString() + "SecondLife.log"; std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log"); LLFile::copy(per_run_file, log_file); -- cgit v1.2.3 From 5b7929364b53ed1e4798ff4c7904014b6f423c0c Mon Sep 17 00:00:00 2001 From: Aura Linden Date: Tue, 25 Mar 2014 18:30:39 -0700 Subject: Still wasn't doing the right thing with log files. --- indra/win_crash_logger/llcrashloggerwindows.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'indra/win_crash_logger/llcrashloggerwindows.cpp') diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index 6b5ab111ec..9fd66d5421 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -320,9 +320,15 @@ int LLCrashLoggerWindows::processingLoop() { llinfos << "session ending.." << llendl; - std::string per_run_file = options["dumpdir"].asString() + "SecondLife.log"; + std::string per_run_dir = options["dumpdir"].asString(); + std::string per_run_file = per_run_dir + "\\SecondLife.log"; std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log"); - LLFile::copy(per_run_file, log_file); + + if (gDirUtilp->fileExists(per_run_dir)) + { + LL_INFOS ("CRASHREPORT") << "Copying " << log_file << " to " << per_run_file << llendl; + LLFile::copy(log_file, per_run_file); + } return 0; } -- cgit v1.2.3