diff options
author | apatrick@google.com <apatrick@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-03 19:49:35 +0000 |
---|---|---|
committer | apatrick@google.com <apatrick@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-03 19:49:35 +0000 |
commit | 91240c947358b33cc19c0923a06d116ab36737dd (patch) | |
tree | 7897c99ad9b41d7735c7a42e0ea75fe6f76760b3 | |
parent | 2e25a9f4152a90a179ad37206bff79e1198379d7 (diff) | |
download | chromium_src-91240c947358b33cc19c0923a06d116ab36737dd.zip chromium_src-91240c947358b33cc19c0923a06d116ab36737dd.tar.gz chromium_src-91240c947358b33cc19c0923a06d116ab36737dd.tar.bz2 |
Asynchronous tick now uses NPN_PluginAsyncCall.URL streaming callbacks are now also asynchronous.Implemented NPN_PluginAsyncCall for IE.Allowed WM_PAINT handler to be reentered because it no longer calls into the browser (except to schedule an asynchronous tick if none is pending).Fixed a bug where the EventManager would crash if an event callback called cleanUp on the client.Cleanup destroys all the packs. Doing this in NPP_Destroy seems to make Chrome timeout and fail to load the next page.Tar and GZ decoding happens on a new thread.
Review URL: http://codereview.chromium.org/155733
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22305 0039d316-1c4b-4281-b951-d872f2087c98
73 files changed, 1844 insertions, 361 deletions
@@ -1,7 +1,7 @@ vars = { "chromium_trunk": "http://src.chromium.org/svn/trunk", - "nixysa_rev": "28", + "nixysa_rev": "29", "o3d_code_rev": "109", "skia_rev": "279", "gyp_rev": "553", @@ -39,6 +39,9 @@ deps = { "o3d/third_party/jsdoctoolkit": "http://o3d.googlecode.com/svn/trunk/googleclient/third_party/jsdoctoolkit@" + Var("o3d_code_rev"), + "o3d/third_party/libevent": + "http://o3d.googlecode.com/svn/trunk/googleclient/third_party/libevent@" + Var("o3d_code_rev"), + "o3d/third_party/pdiff": "http://o3d.googlecode.com/svn/trunk/googleclient/third_party/pdiff@" + Var("o3d_code_rev"), diff --git a/o3d/base/build.scons b/o3d/base/build.scons index f6cd4a0..7898600 100644 --- a/o3d/base/build.scons +++ b/o3d/base/build.scons @@ -46,16 +46,26 @@ chrome_base_inputs = [ 'debug_util', 'file_path', 'file_util', + 'histogram', + 'lazy_instance', 'lock', 'logging', + 'message_loop', + 'message_pump_default', 'path_service', + 'ref_counted', + 'stats_table', 'string_piece', 'string_util', 'string_util_icu', + 'system_monitor', + 'thread_collision_warner', 'third_party/dmg_fp/dtoa', 'third_party/dmg_fp/g_fmt', 'third_party/nspr/prtime', 'thread', + 'tracked', + 'tracked_objects', ] inputs = [ @@ -74,6 +84,7 @@ if env.Bit('windows'): 'process_util_win', 'registry', 'shared_memory_win', + 'sys_info_win', 'sys_string_conversions_win', 'system_monitor_win', 'thread_local_storage_win', @@ -83,23 +94,13 @@ if env.Bit('windows'): 'win_util', ] # The following are cross-platform, but are not needed on linux or mac, and - # pull in more third-party dependencies (libevent) on those platforms, so + # pull in more third-party dependencies on those platforms, so # they are not built on all platforms currently. # TODO: get the dependencies straightened out. chrome_base_inputs += [ 'cpu', - 'histogram', - 'lazy_instance', - 'message_loop', - 'message_pump_default', - 'ref_counted', - 'stats_table', - 'system_monitor', - 'thread_collision_warner', 'time', 'timer', - 'tracked', - 'tracked_objects', ] chrome_base_inputs_posix = [ @@ -108,12 +109,17 @@ chrome_base_inputs_posix = [ 'debug_util_posix', 'file_util_posix', 'lock_impl_posix', + 'message_pump_libevent', 'platform_thread_posix', + 'process_util_posix', + 'shared_memory_posix', 'string16', + 'sys_info_posix', 'time', 'time_posix', 'thread_local_posix', 'thread_local_storage_posix', + 'waitable_event_posix', ] chrome_base_inputs_mm = [] @@ -122,6 +128,8 @@ if env.Bit('linux'): chrome_base_inputs += [ 'base_paths_linux', 'file_util_linux', + 'message_pump_glib', + 'process_util_linux', 'sys_string_conversions_linux', ] @@ -130,7 +138,10 @@ if env.Bit('mac'): chrome_base_inputs_mm += [ 'base_paths_mac', 'file_util_mac', + 'message_pump_mac', 'platform_thread_mac', + 'process_util_mac', + 'scoped_nsautorelease_pool', 'sys_string_conversions_mac', ] diff --git a/o3d/converter/build.scons b/o3d/converter/build.scons index 0a2006d..fd0d8c7 100644 --- a/o3d/converter/build.scons +++ b/o3d/converter/build.scons @@ -47,6 +47,7 @@ env.Append( 'o3dCorePlatform', 'o3dSerializer', 'o3dSerializationObjects', + 'o3dCore', # Archive and Core are mutually dependent so Core appears twice. 'o3dUtils', 'o3d_base', 'FColladaU', @@ -80,9 +81,20 @@ if env.Bit('mac'): FRAMEWORKS = [ 'ApplicationServices', 'Cg', + 'Cocoa', 'Foundation', 'OpenGL', - ], + ], + LIBS = [ + 'event', + ], + ) + +if env.Bit('linux'): + env.Append( + LIBS = [ + 'event', + ], ) inputs = [ diff --git a/o3d/converter/cross/converter.cc b/o3d/converter/cross/converter.cc index 9abde79..ac1b16e 100644 --- a/o3d/converter/cross/converter.cc +++ b/o3d/converter/cross/converter.cc @@ -256,9 +256,7 @@ bool Convert(const FilePath& in_filename, // the loading process. AddBinaryElements(collada, &archive_generator); - archive_generator.Finalize(); - - file_util::CloseFile(out_file); + archive_generator.Close(true); pack->Destroy(); if (error_messages) { diff --git a/o3d/core/build.scons b/o3d/core/build.scons index 088ebce..593b301 100644 --- a/o3d/core/build.scons +++ b/o3d/core/build.scons @@ -66,6 +66,7 @@ cross_inputs = [ 'cross/iclass_manager.cc', 'cross/ierror_status.cc', 'cross/id_manager.cc', + 'cross/imain_thread_task_poster.cc', 'cross/material.cc', 'cross/math_utilities.cc', 'cross/matrix4_axis_rotation.cc', diff --git a/o3d/core/core.gyp b/o3d/core/core.gyp index 7bd7926..2099791 100644 --- a/o3d/core/core.gyp +++ b/o3d/core/core.gyp @@ -126,6 +126,8 @@ 'cross/id_manager.h', 'cross/ierror_status.cc', 'cross/ierror_status.h', + 'cross/imain_thread_task_poster.cc', + 'cross/imain_thread_task_poster.h', 'cross/install_check.h', 'cross/lost_resource_callback.h', 'cross/material.cc', @@ -365,6 +367,7 @@ 'cross/draw_pass_test.cc', 'cross/effect_test.cc', 'cross/element_test.cc', + 'cross/event_manager_test.cc', 'cross/features_test.cc', 'cross/field_test.cc', 'cross/float_n_test.cc', diff --git a/o3d/core/cross/client.cc b/o3d/core/cross/client.cc index ed5fea3..7b70496 100644 --- a/o3d/core/cross/client.cc +++ b/o3d/core/cross/client.cc @@ -139,6 +139,18 @@ void Client::Cleanup() { ClearTickCallback(); event_manager_.ClearAll(); counter_manager_.ClearAllCallbacks(); + + // Disable continuous rendering because there is nothing to render after + // Cleanup is called. This speeds up the the process of unloading the page. + // It also preserves the last rendered frame so long as it does not become + // invalid. + render_mode_ = RENDERMODE_ON_DEMAND; + + // Destroy the packs here if possible. If there are a lot of objects it takes + // a long time and seems to make Chrome timeout if it happens in NPP_Destroy. + root_.Reset(); + rendergraph_root_.Reset(); + object_manager_->DestroyAllPacks(); } Pack* Client::CreatePack() { diff --git a/o3d/core/cross/client.h b/o3d/core/cross/client.h index 16ffbf4..07e60e5 100644 --- a/o3d/core/cross/client.h +++ b/o3d/core/cross/client.h @@ -400,7 +400,6 @@ class Client { // Returns true on success and false on failure. bool SaveScreen(const String& file_name); -#ifdef OS_WIN // This class is intended to be used on the stack, such that the variable gets // incremented on scope entry and decremented on scope exit. It's currently // used in WindowProc to determine if we're reentrant or not, but may be @@ -431,7 +430,6 @@ class Client { Client *client_; DISALLOW_COPY_AND_ASSIGN(ScopedIncrement); }; -#endif private: @@ -499,9 +497,7 @@ class Client { // The id of the client. Id id_; -#ifdef OS_WIN int calls_; // Used to check reentrancy along with ScopedIncrement. -#endif DISALLOW_COPY_AND_ASSIGN(Client); }; // Client diff --git a/o3d/core/cross/event_manager.cc b/o3d/core/cross/event_manager.cc index 0712e4c..f20c7c2 100644 --- a/o3d/core/cross/event_manager.cc +++ b/o3d/core/cross/event_manager.cc @@ -51,9 +51,14 @@ void EventManager::ProcessQueue() { DCHECK(!processing_event_queue_); processing_event_queue_ = true; #endif - const Event& event = event_queue_.front(); - event_callbacks_[event.type()].Run(event); + Event event = event_queue_.front(); + + // Pop the event before invoking the callback; the callback might invoke + // Client::CleanUp, which empties the event queue. This can happen in Chrome + // if it invokes the unload handler when control enters JavaScript. event_queue_.pop_front(); + + event_callbacks_[event.type()].Run(event); #ifndef NDEBUG processing_event_queue_ = false; #endif diff --git a/o3d/core/cross/event_manager_test.cc b/o3d/core/cross/event_manager_test.cc new file mode 100644 index 0000000..c603965 --- /dev/null +++ b/o3d/core/cross/event_manager_test.cc @@ -0,0 +1,78 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + + +// This file implements unit tests for class EventManager. + +#include "tests/common/win/testing_common.h" +#include "core/cross/event_manager.h" + +namespace o3d { + +class EventManagerTest : public testing::Test { + protected: + EventManagerTest() { + } + + virtual void SetUp(); + virtual void TearDown(); + + EventManager event_manager_; +}; + +void EventManagerTest::SetUp() { +} + +void EventManagerTest::TearDown() { + event_manager_.ClearAll(); +} + +TEST_F(EventManagerTest, CanClearAllFromEventCallback) { + class ClearAllEventCallback : public EventCallback { + public: + explicit ClearAllEventCallback(EventManager* event_manager) + : event_manager_(event_manager) { + } + virtual void Run(const Event& event) { + event_manager_->ClearAll(); + } + private: + EventManager* event_manager_; + }; + + event_manager_.SetEventCallback( + Event::TYPE_CLICK, + new ClearAllEventCallback(&event_manager_)); + Event event(Event::TYPE_CLICK); + event_manager_.AddEventToQueue(event); + event_manager_.ProcessQueue(); +} +} // namespace o3d diff --git a/o3d/core/cross/imain_thread_task_poster.cc b/o3d/core/cross/imain_thread_task_poster.cc new file mode 100644 index 0000000..8738efb --- /dev/null +++ b/o3d/core/cross/imain_thread_task_poster.cc @@ -0,0 +1,41 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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 "core/cross/precompile.h" +#include "core/cross/imain_thread_task_poster.h" + +namespace o3d { + +const InterfaceId IMainThreadTaskPoster::kInterfaceId = + InterfaceTraits<IMainThreadTaskPoster>::kInterfaceId; + +} // namespace o3d diff --git a/o3d/core/cross/imain_thread_task_poster.h b/o3d/core/cross/imain_thread_task_poster.h new file mode 100644 index 0000000..cd3d26e --- /dev/null +++ b/o3d/core/cross/imain_thread_task_poster.h @@ -0,0 +1,57 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + + +#ifndef O3D_CORE_CROSS_IMAIN_THREAD_TASK_POSTER_H_ +#define O3D_CORE_CROSS_IMAIN_THREAD_TASK_POSTER_H_ + +#include "base/task.h" +#include "core/cross/service_locator.h" + +namespace o3d { + +// Allows tasks to be posted from one thread to the main thread. +class IMainThreadTaskPoster { + public: + static const InterfaceId kInterfaceId; + + IMainThreadTaskPoster() {} + virtual ~IMainThreadTaskPoster() {} + + virtual bool IsSupported() = 0; + virtual void PostTask(Task* task) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(IMainThreadTaskPoster); +}; +} + +#endif // O3D_CORE_CROSS_IMAIN_THREAD_TASK_POSTER_H_ diff --git a/o3d/core/cross/message_queue_test.cc b/o3d/core/cross/message_queue_test.cc index 77ba145..7fa43e6 100644 --- a/o3d/core/cross/message_queue_test.cc +++ b/o3d/core/cross/message_queue_test.cc @@ -36,13 +36,11 @@ #include "core/cross/client.h" #include "core/cross/types.h" #include "tests/common/win/testing_common.h" -#include "base/at_exit.h" #include "base/condition_variable.h" #include "base/lock.h" #include "base/platform_thread.h" #include "base/time.h" -using ::base::AtExitManager; using ::base::Time; using ::base::TimeDelta; @@ -286,7 +284,6 @@ void MessageQueueTest::RunTests(int num_threads, MessageQueue* message_queue = new MessageQueue(g_service_locator); message_queue->Initialize(); - AtExitManager manager; TimeSource* time_source = new WallClockTimeSource(); TestWatchdog* watchdog = new TestWatchdog(num_threads, timeout, diff --git a/o3d/import/archive.gyp b/o3d/import/archive.gyp index 73e12fb..b2e2fc6 100644 --- a/o3d/import/archive.gyp +++ b/o3d/import/archive.gyp @@ -37,6 +37,8 @@ 'cross/memory_buffer.h', 'cross/memory_stream.cc', 'cross/memory_stream.h', + 'cross/main_thread_archive_callback_client.cc', + 'cross/main_thread_archive_callback_client.h', 'cross/raw_data.cc', 'cross/raw_data.h', 'cross/tar_processor.cc', @@ -44,6 +46,8 @@ 'cross/targz_generator.h', 'cross/targz_processor.cc', 'cross/targz_processor.h', + 'cross/threaded_stream_processor.cc', + 'cross/threaded_stream_processor.h', ], }, { @@ -61,6 +65,7 @@ 'cross/raw_data_test.cc', 'cross/tar_processor_test.cc', 'cross/targz_processor_test.cc', + 'thread_stream_processor_test.cc', ], }, 'copies': [ diff --git a/o3d/import/build.scons b/o3d/import/build.scons index c0789032..8aa5a88 100644 --- a/o3d/import/build.scons +++ b/o3d/import/build.scons @@ -84,9 +84,11 @@ archive_inputs = [ 'cross/archive_request.cc', 'cross/gz_decompressor.cc', 'cross/memory_stream.cc', + 'cross/main_thread_archive_callback_client.cc', 'cross/raw_data.cc', 'cross/tar_processor.cc', 'cross/targz_processor.cc', + 'cross/threaded_stream_processor.cc', ] conditioner_inputs = ['cross/collada_conditioner.cc'] diff --git a/o3d/import/cross/archive_processor.cc b/o3d/import/cross/archive_processor.cc index 8601695..a10af1b 100644 --- a/o3d/import/cross/archive_processor.cc +++ b/o3d/import/cross/archive_processor.cc @@ -41,72 +41,30 @@ const int kChunkSize = 16384; namespace o3d { -#ifdef _DEBUG // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// For debugging only, report a zlib or i/o error -void zerr(int ret) { - LOG(ERROR) << "ArchiveProcessor: "; - - switch (ret) { - case Z_ERRNO: - if (ferror(stdin)) - LOG(ERROR) << "error reading stdin\n"; - if (ferror(stdout)) - LOG(ERROR) << "error writing stdout\n"; - break; - case Z_STREAM_ERROR: - LOG(ERROR) << "invalid compression level\n"; - break; - case Z_DATA_ERROR: - LOG(ERROR) << "invalid or incomplete deflate data\n"; - break; - case Z_MEM_ERROR: - LOG(ERROR) << "out of memory\n"; - break; - case Z_VERSION_ERROR: - LOG(ERROR) << "zlib version mismatch!\n"; - break; - } -} -#endif // _DEBUG - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -int ArchiveProcessor::ProcessEntireStream(MemoryReadStream *stream) { - int result = Z_OK; - bool has_error = false; +StreamProcessor::Status ArchiveProcessor::ProcessEntireStream( + MemoryReadStream *stream) { + Status status; // decompress until deflate stream ends or error do { int remaining = stream->GetRemainingByteCount(); int process_this_time = remaining < kChunkSize ? remaining : kChunkSize; - result = ProcessCompressedBytes(stream, process_this_time); - - has_error = (result != Z_OK && result != Z_STREAM_END); + status = ProcessBytes(stream, process_this_time); + } while (status == IN_PROGRESS); -#ifdef _DEBUG - if (has_error) { - zerr(result); - } -#endif - } while (result != Z_STREAM_END && !has_error); - - if (result == Z_STREAM_END) { - // if we got to the end of stream, then we're good... - result = Z_OK; - } - - return result; + return status; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -int ArchiveProcessor::ProcessFile(const char *filename) { +StreamProcessor::Status ArchiveProcessor::ProcessFile(const char *filename) { struct stat file_info; int result = stat(filename, &file_info); - if (result != 0) return -1; + if (result != 0) return FAILURE; int file_length = file_info.st_size; - if (file_length == 0) return -1; + if (file_length == 0) return FAILURE; MemoryBuffer<uint8> buffer; buffer.Allocate(file_length); @@ -115,20 +73,13 @@ int ArchiveProcessor::ProcessFile(const char *filename) { // Test by reading in a tar.gz file and sending through the // progressive streaming system FILE *fp = fopen(filename, "rb"); - if (!fp) return -1; // can't open file! + if (!fp) return FAILURE; // can't open file! fread(p, sizeof(uint8), file_length, fp); fclose(fp); MemoryReadStream stream(p, file_length); - result = ProcessEntireStream(&stream); - - if (result == Z_STREAM_END) { - // if we got to the end of stream, then we're good... - result = Z_OK; - } - - return result; + return ProcessEntireStream(&stream); } } // namespace o3d diff --git a/o3d/import/cross/archive_processor.h b/o3d/import/cross/archive_processor.h index 8380c37..d404249 100644 --- a/o3d/import/cross/archive_processor.h +++ b/o3d/import/cross/archive_processor.h @@ -53,8 +53,6 @@ class ArchiveFileInfo { private: std::string filename_; int file_size_; - - DISALLOW_COPY_AND_ASSIGN(ArchiveFileInfo); }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -63,10 +61,11 @@ class ArchiveCallbackClient { virtual ~ArchiveCallbackClient() {} virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info) = 0; virtual bool ReceiveFileData(MemoryReadStream *stream, size_t nbytes) = 0; + virtual void Close(bool success) = 0; }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class ArchiveProcessor { +class ArchiveProcessor: public StreamProcessor { public: explicit ArchiveProcessor(ArchiveCallbackClient *callback_client) {} @@ -75,25 +74,16 @@ class ArchiveProcessor { // Call to "push" bytes into the processor. They will be decompressed and // the appropriate callbacks on |callback_client| will happen // as files come in... - // - // Return values (using zlib error codes): - // Z_OK : Processing was successful - but not yet done - // Z_STREAM_END : We're done - archive is completely/successfully processed - // any other value indicates an error condition - // - // Note: even archive formats not based on zlib should use these codes - // (Z_OK, Z_STREAM_END) - // - virtual int ProcessCompressedBytes(MemoryReadStream *stream, - size_t bytes_to_process) = 0; + virtual Status ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) = 0; // Decompresses the complete file archive, making file callbacks as the files // come in... - virtual int ProcessFile(const char *filename); + virtual Status ProcessFile(const char *filename); // Decompresses the complete archive from memory, // making file callbacks as the files come in... - virtual int ProcessEntireStream(MemoryReadStream *stream); + virtual Status ProcessEntireStream(MemoryReadStream *stream); protected: DISALLOW_COPY_AND_ASSIGN(ArchiveProcessor); diff --git a/o3d/import/cross/archive_request.cc b/o3d/import/cross/archive_request.cc index 84448e4..c02e1a5 100644 --- a/o3d/import/cross/archive_request.cc +++ b/o3d/import/cross/archive_request.cc @@ -33,8 +33,11 @@ #include "import/cross/archive_request.h" -#include "import/cross/targz_processor.h" #include "core/cross/pack.h" +#include "core/cross/imain_thread_task_poster.h" +#include "import/cross/targz_processor.h" +#include "import/cross/main_thread_archive_callback_client.h" +#include "import/cross/threaded_stream_processor.h" #define DEBUG_ARCHIVE_CALLBACKS 0 @@ -61,12 +64,25 @@ ArchiveRequest::ArchiveRequest(ServiceLocator* service_locator, ready_state_(0), stream_length_(0), bytes_received_(0) { - archive_processor_ = new TarGzProcessor(this); + IMainThreadTaskPoster* main_thread_task_poster = + service_locator->GetService<IMainThreadTaskPoster>(); + if (main_thread_task_poster->IsSupported()) { + main_thread_archive_callback_client_ = new MainThreadArchiveCallbackClient( + service_locator, this); + extra_processor_ = new TarGzProcessor(main_thread_archive_callback_client_); + archive_processor_ = new ThreadedStreamProcessor(extra_processor_); + } else { + main_thread_archive_callback_client_ = NULL; + extra_processor_ = NULL; + archive_processor_ = new TarGzProcessor(this); + } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ArchiveRequest::~ArchiveRequest() { delete archive_processor_; + delete extra_processor_; + delete main_thread_archive_callback_client_; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -85,7 +101,7 @@ void ArchiveRequest::NewStreamCallback(DownloadStream *stream) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ int32 ArchiveRequest::WriteReadyCallback(DownloadStream *stream) { // Setting this too high causes Firefox to timeout in the Write callback. - return 1024; + return 128 * 1024; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -99,10 +115,10 @@ int32 ArchiveRequest::WriteCallback(DownloadStream *stream, MemoryReadStream memory_stream(reinterpret_cast<uint8*>(data), length); // Progressively decompress the bytes we've just been given - int result = - archive_processor_->ProcessCompressedBytes(&memory_stream, length); + StreamProcessor::Status status = + archive_processor_->ProcessBytes(&memory_stream, length); - if (result != Z_OK && result != Z_STREAM_END) { + if (status == StreamProcessor::FAILURE) { set_success(false); set_error("Invalid gzipped tar file"); stream->Cancel(); // tell the browser to stop downloading @@ -120,21 +136,7 @@ void ArchiveRequest::FinishedCallback(DownloadStream *stream, bool success, const std::string &filename, const std::string &mime_type) { - set_ready_state(ArchiveRequest::STATE_LOADED); - - // Since the standard codes only go far enough to tell us that the download - // succeeded, we set the success [and implicitly the done] flags to give the - // rest of the story. - set_success(success); - if (!success) { - // I have no idea if an error is already set here but one MUST be set - // so let's check. - if (error().empty()) { - set_error(String("Could not download archive: ") + uri()); - } - } - if (onreadystatechange()) - onreadystatechange()->Run(); + archive_processor_->Close(success); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -212,7 +214,7 @@ bool ArchiveRequest::ReceiveFileData(MemoryReadStream *input_stream, return false; } } else { - onfileavailable()->Run(); + onfileavailable()->Run(raw_data_); } // If data hasn't been discarded (inside callback) then writes out to @@ -227,4 +229,22 @@ bool ArchiveRequest::ReceiveFileData(MemoryReadStream *input_stream, return true; } +void ArchiveRequest::Close(bool success) { + set_ready_state(ArchiveRequest::STATE_LOADED); + + // Since the standard codes only go far enough to tell us that the download + // succeeded, we set the success [and implicitly the done] flags to give the + // rest of the story. + set_success(success); + if (!success) { + // I have no idea if an error is already set here but one MUST be set + // so let's check. + if (error().empty()) { + set_error(String("Could not download archive: ") + uri()); + } + } + if (onreadystatechange()) + onreadystatechange()->Run(); +} + } // namespace o3d diff --git a/o3d/import/cross/archive_request.h b/o3d/import/cross/archive_request.h index 0a73864..65158bb 100644 --- a/o3d/import/cross/archive_request.h +++ b/o3d/import/cross/archive_request.h @@ -50,7 +50,8 @@ namespace o3d { -typedef Closure ArchiveRequestCallback; +typedef Closure ArchiveReadyStateChangeCallback; +typedef Callback1<RawData*> ArchiveFileAvailableCallback; // An ArchiveRequest object is used to carry out an asynchronous request // for a file to be loaded. @@ -104,22 +105,24 @@ class ArchiveRequest : public ObjectBase, public ArchiveCallbackClient { // ArchiveCallbackClient methods virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info); virtual bool ReceiveFileData(MemoryReadStream *stream, size_t nbytes); + virtual void Close(bool success); Pack *pack() { return pack_.Get(); // Set at creation time and never changed. } - ArchiveRequestCallback *onfileavailable() { + ArchiveFileAvailableCallback *onfileavailable() { return onfileavailable_.get(); } - void set_onfileavailable(ArchiveRequestCallback *onfileavailable) { + void set_onfileavailable(ArchiveFileAvailableCallback *onfileavailable) { onfileavailable_.reset(onfileavailable); } - ArchiveRequestCallback *onreadystatechange() { + ArchiveReadyStateChangeCallback *onreadystatechange() { return onreadystatechange_.get(); } - void set_onreadystatechange(ArchiveRequestCallback *onreadystatechange) { + void set_onreadystatechange( + ArchiveReadyStateChangeCallback *onreadystatechange) { onreadystatechange_.reset(onreadystatechange); } @@ -174,8 +177,8 @@ class ArchiveRequest : public ObjectBase, public ArchiveCallbackClient { ArchiveRequest(ServiceLocator* service_locator, Pack *pack); Pack::Ref pack_; - scoped_ptr<ArchiveRequestCallback> onreadystatechange_; - scoped_ptr<ArchiveRequestCallback> onfileavailable_; + scoped_ptr<ArchiveReadyStateChangeCallback> onreadystatechange_; + scoped_ptr<ArchiveFileAvailableCallback> onfileavailable_; String uri_; // Request state @@ -184,7 +187,9 @@ class ArchiveRequest : public ObjectBase, public ArchiveCallbackClient { int ready_state_; // Like the XMLHttpRequest variable of the same name. String error_; // Set after completion on failure. - TarGzProcessor *archive_processor_; + StreamProcessor *archive_processor_; + StreamProcessor *extra_processor_; + ArchiveCallbackClient* main_thread_archive_callback_client_; std::vector<RawData::Ref> raw_data_list_; RawData::Ref raw_data_; MemoryBuffer<uint8> temp_buffer_; diff --git a/o3d/import/cross/file_output_stream_processor.cc b/o3d/import/cross/file_output_stream_processor.cc index 3983faa..eab955b 100644 --- a/o3d/import/cross/file_output_stream_processor.cc +++ b/o3d/import/cross/file_output_stream_processor.cc @@ -41,12 +41,20 @@ FileOutputStreamProcessor::FileOutputStreamProcessor(FILE* file) DCHECK(file != NULL); } -int FileOutputStreamProcessor::ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process) { +StreamProcessor::Status FileOutputStreamProcessor::ProcessBytes( + MemoryReadStream *stream, + size_t bytes_to_process) { + DCHECK(file_ != NULL); size_t num_written = fwrite(stream->GetDirectMemoryPointer(), 1, bytes_to_process, file_); - return num_written == bytes_to_process ? 0 : -1; + return num_written == bytes_to_process ? IN_PROGRESS : FAILURE; } + +void FileOutputStreamProcessor::Close(bool success) { + fclose(file_); + file_ = NULL; +} + } // namespace o3d diff --git a/o3d/import/cross/file_output_stream_processor.h b/o3d/import/cross/file_output_stream_processor.h index 3c04562..ff3b24e 100644 --- a/o3d/import/cross/file_output_stream_processor.h +++ b/o3d/import/cross/file_output_stream_processor.h @@ -44,8 +44,10 @@ class FileOutputStreamProcessor : public StreamProcessor { public: explicit FileOutputStreamProcessor(FILE* file); - virtual int ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process); + virtual Status ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); + virtual void Close(bool success); + private: FILE* file_; DISALLOW_COPY_AND_ASSIGN(FileOutputStreamProcessor); diff --git a/o3d/import/cross/gz_compressor.cc b/o3d/import/cross/gz_compressor.cc index e42f2e7..d6a98cf 100644 --- a/o3d/import/cross/gz_compressor.cc +++ b/o3d/import/cross/gz_compressor.cc @@ -54,17 +54,19 @@ GzCompressor::GzCompressor(StreamProcessor *callback_client) strm_.opaque = Z_NULL; // Store this, so we can later check if it's OK to start processing - init_result_ = deflateInit2( + int result = deflateInit2( &strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, // 16 means write out gzip header/trailer DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + + initialized_ = result == Z_OK; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -void GzCompressor::Finalize() { +void GzCompressor::Close(bool success) { if (!stream_is_closed_) { // Flush the compression stream MemoryReadStream stream(NULL, 0); @@ -74,39 +76,38 @@ void GzCompressor::Finalize() { deflateEnd(&strm_); stream_is_closed_ = true; } + + callback_client_->Close(success); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GzCompressor::~GzCompressor() { - // Finalize() turns out to be a "nop" if the user has already called it - Finalize(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -int GzCompressor::ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process) { +StreamProcessor::Status GzCompressor::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { // Basic sanity check if (stream->GetDirectMemoryPointer() == NULL || bytes_to_process == 0) { - return -1; + return FAILURE; } return CompressBytes(stream, bytes_to_process, false); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -int GzCompressor::CompressBytes(MemoryReadStream *stream, - size_t bytes_to_process, - bool flush) { +StreamProcessor::Status GzCompressor::CompressBytes(MemoryReadStream *stream, + size_t bytes_to_process, + bool flush) { // Don't even bother trying if we didn't get initialized properly - if (init_result_ != Z_OK) return init_result_; + if (!initialized_) return FAILURE; uint8 out[kChunkSize]; - int result = Z_OK; // Don't try to read more than our stream has size_t remaining = stream->GetRemainingByteCount(); if (bytes_to_process > remaining) { - return Z_STREAM_ERROR; + return FAILURE; } // Use direct memory access on the MemoryStream object @@ -120,30 +121,29 @@ int GzCompressor::CompressBytes(MemoryReadStream *stream, // We need to flush the stream when we reach the end int flush_code = flush ? Z_FINISH : Z_NO_FLUSH; - // Run inflate() on input until output buffer not full + // Run deflate() on input until output buffer not full + int result; do { strm_.avail_out = kChunkSize; strm_.next_out = out; result = deflate(&strm_, flush_code); - - // error check here - return error codes if necessary - assert(result != Z_STREAM_ERROR); // state not clobbered + if (result == Z_STREAM_ERROR) + return FAILURE; size_t have = kChunkSize - strm_.avail_out; // Callback with the compressed byte stream MemoryReadStream decompressed_stream(out, have); if (have > 0 && callback_client_) { - int client_result = - callback_client_->ProcessBytes(&decompressed_stream, have); - if (client_result != 0) { - return client_result; // propagate callback errors + if (callback_client_->ProcessBytes(&decompressed_stream, + have) == FAILURE) { + return FAILURE; } } } while (strm_.avail_out == 0); - return result; + return result == Z_OK ? IN_PROGRESS : FAILURE; } } // namespace o3d diff --git a/o3d/import/cross/gz_compressor.h b/o3d/import/cross/gz_compressor.h index bdf13a1..43dbf0f 100644 --- a/o3d/import/cross/gz_compressor.h +++ b/o3d/import/cross/gz_compressor.h @@ -49,18 +49,19 @@ class GzCompressor : public StreamProcessor { explicit GzCompressor(StreamProcessor *callback_client); virtual ~GzCompressor(); - virtual int ProcessBytes(MemoryReadStream *stream, size_t bytes_to_process); + virtual Status ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); // Must call when all bytes to compress have been sent (with ProcessBytes) - void Finalize(); + virtual void Close(bool success); private: - int CompressBytes(MemoryReadStream *stream, - size_t bytes_to_process, - bool flush); + Status CompressBytes(MemoryReadStream *stream, + size_t bytes_to_process, + bool flush); z_stream strm_; // low-level zlib state - int init_result_; + bool initialized_; bool stream_is_closed_; StreamProcessor *callback_client_; diff --git a/o3d/import/cross/gz_compressor_test.cc b/o3d/import/cross/gz_compressor_test.cc index 787ab06..4cebb004 100644 --- a/o3d/import/cross/gz_compressor_test.cc +++ b/o3d/import/cross/gz_compressor_test.cc @@ -56,10 +56,12 @@ class DecompressorClient : public o3d::StreamProcessor { public: explicit DecompressorClient(size_t uncompressed_byte_size) : buffer_(uncompressed_byte_size), - write_stream_(buffer_, uncompressed_byte_size) {} + write_stream_(buffer_, uncompressed_byte_size), + closed_(false), + success_(false) {} - virtual int ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process) { + virtual Status ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { // Make sure the output stream isn't full yet EXPECT_TRUE(write_stream_.GetRemainingByteCount() >= bytes_to_process); @@ -68,7 +70,12 @@ class DecompressorClient : public o3d::StreamProcessor { stream->Skip(bytes_to_process); write_stream_.Write(p, bytes_to_process); - return 0; // return OK + return SUCCESS; + } + + virtual void Close(bool success) { + closed_ = true; + success_ = success; } void VerifyDecompressedOutput(uint8 *original_data) { @@ -80,10 +87,20 @@ class DecompressorClient : public o3d::StreamProcessor { EXPECT_EQ(0, memcmp(original_data, buffer_, buffer_.GetLength())); } + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + private: size_t uncompressed_byte_size_; MemoryBuffer<uint8> buffer_; MemoryWriteStream write_stream_; + bool closed_; + bool success_; }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -92,24 +109,27 @@ class CompressorClient : public o3d::StreamProcessor { public: explicit CompressorClient(size_t uncompressed_byte_size) : decompressor_client_(uncompressed_byte_size), - decompressor_(&decompressor_client_) { + decompressor_(&decompressor_client_), + closed_(false), + success_(false) { }; - virtual int ProcessBytes(MemoryReadStream *stream, + virtual Status ProcessBytes(MemoryReadStream *stream, size_t bytes_to_process) { // We're receiving compressed bytes here, so feed them back into // the decompressor. Since we're making a compression / decompression // round trip, we should end up with the same (initial) byte stream // we can verify this at the end - int result = decompressor_.ProcessBytes(stream, bytes_to_process); + Status status = decompressor_.ProcessBytes(stream, bytes_to_process); - // Verify the result code: - // zlib FAQ says Z_BUF_ERROR is OK - // and may occur in the middle of decompressing the stream - EXPECT_TRUE(result == Z_OK || result == Z_STREAM_END || - result == Z_BUF_ERROR); + EXPECT_TRUE(status == IN_PROGRESS || status == SUCCESS); + + return SUCCESS; + } - return 0; // no error + virtual void Close(bool success) { + closed_ = true; + success_ = success; } void VerifyDecompressedOutput(uint8 *original_data) { @@ -117,15 +137,24 @@ class CompressorClient : public o3d::StreamProcessor { decompressor_client_.VerifyDecompressedOutput(original_data); } + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + private: DecompressorClient decompressor_client_; o3d::GzDecompressor decompressor_; + bool closed_; + bool success_; }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// int main (int argc, const char * argv[]) { TEST_F(GzCompressorTest, RoundTripCompressionDecompression) { // We'll compress this file String filepath = *g_program_path + "/archive_files/keyboard.jpg"; @@ -148,14 +177,27 @@ TEST_F(GzCompressorTest, RoundTripCompressionDecompression) { // Since we're making a compression / decompression // round trip, we should end up with the same (initial) byte stream // we can verify this at the end by calling VerifyDecompressedOutput() - int result = compressor.ProcessBytes(&input_file_stream, file_length); - EXPECT_TRUE(result == Z_OK || result == Z_STREAM_END); + StreamProcessor::Status status = + compressor.ProcessBytes(&input_file_stream, file_length); + EXPECT_NE(StreamProcessor::FAILURE, status); - compressor.Finalize(); + compressor.Close(true); compressor_client.VerifyDecompressedOutput(p); + EXPECT_TRUE(compressor_client.closed()); + EXPECT_TRUE(compressor_client.success()); free(p); } +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +TEST_F(GzCompressorTest, PassesFailureThroughToClient) { + CompressorClient compressor_client(1000); + GzCompressor compressor(&compressor_client); + compressor.Close(false); + + EXPECT_TRUE(compressor_client.closed()); + EXPECT_FALSE(compressor_client.success()); +} + } // namespace o3d diff --git a/o3d/import/cross/gz_decompressor.cc b/o3d/import/cross/gz_decompressor.cc index 7fa7fee..5499958 100644 --- a/o3d/import/cross/gz_decompressor.cc +++ b/o3d/import/cross/gz_decompressor.cc @@ -54,7 +54,7 @@ GzDecompressor::GzDecompressor(StreamProcessor *callback_client) strm_.next_in = Z_NULL; // Store this, so we can later check if it's OK to start processing - init_result_ = inflateInit2(&strm_, 31); + initialized_ = inflateInit2(&strm_, 31) == Z_OK; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -64,10 +64,10 @@ GzDecompressor::~GzDecompressor() { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // -int GzDecompressor::ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process) { +StreamProcessor::Status GzDecompressor::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { // Don't even bother trying if we didn't get initialized properly - if (init_result_ != Z_OK) return init_result_; + if (!initialized_) return FAILURE; uint8 out[kChunkSize]; int result; @@ -76,7 +76,7 @@ int GzDecompressor::ProcessBytes(MemoryReadStream *stream, // Don't try to read more than our stream has size_t remaining = stream->GetRemainingByteCount(); if (bytes_to_process > remaining) { - return Z_STREAM_ERROR; // could have our own error code, but good enough... + return FAILURE; } // Use direct memory access on the MemoryStream object @@ -95,11 +95,9 @@ int GzDecompressor::ProcessBytes(MemoryReadStream *stream, assert(result != Z_STREAM_ERROR); /* state not clobbered */ switch (result) { case Z_NEED_DICT: - result = Z_DATA_ERROR; /* and fall through */ - case Z_DATA_ERROR: case Z_MEM_ERROR: - return result; + return FAILURE; } have = kChunkSize - strm_.avail_out; @@ -107,15 +105,26 @@ int GzDecompressor::ProcessBytes(MemoryReadStream *stream, // Callback with the decompressed byte stream MemoryReadStream decompressed_stream(out, have); if (callback_client_) { - int client_result = - callback_client_->ProcessBytes(&decompressed_stream, have); - if (client_result != 0) { - return client_result; // propagate callback errors + if (callback_client_->ProcessBytes(&decompressed_stream, + have) == FAILURE) { + return FAILURE; } } } while (strm_.avail_out == 0); - return result; + switch (result) { + case Z_OK: + case Z_BUF_ERROR: // Zlib docs say this is expected. + return IN_PROGRESS; + case Z_STREAM_END: + return SUCCESS; + default: + return FAILURE; + } +} + +void GzDecompressor::Close(bool success) { + callback_client_->Close(success); } } // namespace o3d diff --git a/o3d/import/cross/gz_decompressor.h b/o3d/import/cross/gz_decompressor.h index bd56687..02d2c7a 100644 --- a/o3d/import/cross/gz_decompressor.h +++ b/o3d/import/cross/gz_decompressor.h @@ -49,12 +49,13 @@ class GzDecompressor : public StreamProcessor { explicit GzDecompressor(StreamProcessor *callback_client); virtual ~GzDecompressor(); - virtual int ProcessBytes(MemoryReadStream *stream, + virtual Status ProcessBytes(MemoryReadStream *stream, size_t bytes_to_process); + virtual void Close(bool success); private: z_stream strm_; // low-level zlib state - int init_result_; + bool initialized_; StreamProcessor *callback_client_; DISALLOW_COPY_AND_ASSIGN(GzDecompressor); diff --git a/o3d/import/cross/gz_decompressor_test.cc b/o3d/import/cross/gz_decompressor_test.cc index af45ecd..4bb71d6 100644 --- a/o3d/import/cross/gz_decompressor_test.cc +++ b/o3d/import/cross/gz_decompressor_test.cc @@ -51,12 +51,14 @@ class GzDecompressorTest : public testing::Test { // class GzTestClient : public StreamProcessor { public: - explicit GzTestClient(size_t uncompressed_size) { + explicit GzTestClient(size_t uncompressed_size) + : closed_(false), + success_(false) { buffer_.Allocate(uncompressed_size); stream_.Assign(buffer_, uncompressed_size); } - virtual int ProcessBytes(MemoryReadStream *input_stream, + virtual Status ProcessBytes(MemoryReadStream *input_stream, size_t bytes_to_process) { // Buffer the uncompressed bytes we're given const uint8 *p = input_stream->GetDirectMemoryPointer(); @@ -65,16 +67,31 @@ class GzTestClient : public StreamProcessor { size_t bytes_written = stream_.Write(p, bytes_to_process); EXPECT_EQ(bytes_written, bytes_to_process); - return Z_OK; + return SUCCESS; + } + + virtual void Close(bool success) { + closed_ = true; + success_ = success; } // When we're done decompressing, we can check the results here uint8 *GetResultBuffer() { return buffer_; } size_t GetResultLength() const { return stream_.GetStreamPosition(); } + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + private: MemoryBuffer<uint8> buffer_; MemoryWriteStream stream_; + bool closed_; + bool success_; }; // Loads a tar.gz file, runs it through the processor. @@ -109,19 +126,20 @@ TEST_F(GzDecompressorTest, LoadGzFile) { MemoryReadStream compressed_stream(compressed_data, compressed_size); size_t bytes_to_process = compressed_size; - int result = Z_OK; + StreamProcessor::Status status = StreamProcessor::SUCCESS; while (bytes_to_process > 0) { size_t bytes_this_time = bytes_to_process < kChunkSize ? bytes_to_process : kChunkSize; - result = decompressor.ProcessBytes(&compressed_stream, bytes_this_time); - EXPECT_TRUE(result == Z_OK || result == Z_STREAM_END); + status = decompressor.ProcessBytes(&compressed_stream, bytes_this_time); + EXPECT_TRUE(status != StreamProcessor::FAILURE); bytes_to_process -= bytes_this_time; } - // When the decompressor has finished it should return Z_STREAM_END - EXPECT_TRUE(result == Z_STREAM_END); + decompressor.Close(true); + + EXPECT_TRUE(status == StreamProcessor::SUCCESS); // Now let's verify that what we just decompressed matches exactly // what's in the reference file... @@ -130,13 +148,25 @@ TEST_F(GzDecompressorTest, LoadGzFile) { EXPECT_EQ(decompressor_client.GetResultLength(), uncompressed_size); // Now check the data - result = memcmp(decompressor_client.GetResultBuffer(), - expected_uncompressed_data, - uncompressed_size); + int result = memcmp(decompressor_client.GetResultBuffer(), + expected_uncompressed_data, + uncompressed_size); EXPECT_EQ(0, result); + EXPECT_TRUE(decompressor_client.closed()); + EXPECT_TRUE(decompressor_client.success()); + free(compressed_data); free(expected_uncompressed_data); } +TEST_F(GzDecompressorTest, PassesFailureThroughToClient) { + GzTestClient decompressor_client(1000); + GzDecompressor decompressor(&decompressor_client); + decompressor.Close(false); + + EXPECT_TRUE(decompressor_client.closed()); + EXPECT_FALSE(decompressor_client.success()); +} + } // namespace o3d diff --git a/o3d/import/cross/iarchive_generator.h b/o3d/import/cross/iarchive_generator.h index 4f662da..04f3a46 100644 --- a/o3d/import/cross/iarchive_generator.h +++ b/o3d/import/cross/iarchive_generator.h @@ -48,13 +48,16 @@ class IArchiveGenerator { // Call AddFile() for each file entry, followed by calls to AddFileBytes() // for the file's data - virtual void AddFile(const String& file_name, + virtual bool AddFile(const String& file_name, size_t file_size) = 0; // Call with the file's data (after calling AddFile) // may be called one time will all the file's data, or multiple times // until all the data is provided virtual int AddFileBytes(MemoryReadStream* stream, size_t n) = 0; + + // Must be called to finish the archiving operation. + virtual void Close(bool success) = 0; }; } // namespace o3d diff --git a/o3d/import/cross/main_thread_archive_callback_client.cc b/o3d/import/cross/main_thread_archive_callback_client.cc new file mode 100644 index 0000000..2fbbaaa --- /dev/null +++ b/o3d/import/cross/main_thread_archive_callback_client.cc @@ -0,0 +1,138 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + +// MainThreadArchiveCallbackClient forwards data from one thread to +// another client that will run on the main thread. + +#include "core/cross/imain_thread_task_poster.h" +#include "core/cross/service_locator.h" +#include "import/cross/main_thread_archive_callback_client.h" +#include "base/task.h" + +namespace o3d { + +// Called on main thread. +MainThreadArchiveCallbackClient::MainThreadArchiveCallbackClient( + ServiceLocator* service_locator, + ArchiveCallbackClient *receiver) + : main_thread_task_poster_( + service_locator->GetService<IMainThreadTaskPoster>()), + receiver_(receiver), + success_(new Success) { + success_->value = true; +} + +// Called on main thread. +MainThreadArchiveCallbackClient::~MainThreadArchiveCallbackClient() { + // Signal pending tasks on main thread not to call the receiver. They are not + // running concurrently because this destructor is also called on the main + // thread. + success_->value = false; +} + +// Called on other thread. +void MainThreadArchiveCallbackClient::ReceiveFileHeader( + const ArchiveFileInfo &file_info) { + main_thread_task_poster_->PostTask(NewRunnableFunction( + &MainThreadArchiveCallbackClient::ForwardReceiveFileHeader, + success_, + receiver_, + file_info)); +} + +// Called on other thread. +bool MainThreadArchiveCallbackClient::ReceiveFileData( + MemoryReadStream *stream, size_t size) { + // Copy stream into temporary buffer. Deleted after it is received by the main + // thread. + uint8* buffer = new uint8[size]; + stream->Read(buffer, size); + + main_thread_task_poster_->PostTask(NewRunnableFunction( + &MainThreadArchiveCallbackClient::ForwardReceiveFileData, + success_, + receiver_, + buffer, + size)); + + return success_->value; +} + +void MainThreadArchiveCallbackClient::Close(bool success) { + main_thread_task_poster_->PostTask(NewRunnableFunction( + &MainThreadArchiveCallbackClient::ForwardClose, + success_, + receiver_, + success)); +} + +// Called on main thread. +void MainThreadArchiveCallbackClient::ForwardReceiveFileHeader( + SuccessPtr success, + ArchiveCallbackClient* client, + ArchiveFileInfo file_info) { + if (success->value) { + client->ReceiveFileHeader(file_info); + } +} + +// Called on main thread. +void MainThreadArchiveCallbackClient::ForwardReceiveFileData( + SuccessPtr success, + ArchiveCallbackClient* client, + uint8* bytes, size_t size) { + // Just delete the buffer if there was a previous failure. + if (!success->value) { + delete[] bytes; + return; + } + + MemoryReadStream stream(bytes, size); + if (!client->ReceiveFileData(&stream, size)) { + // Do not set this to true on success. That might overwrite a false written + // by the other thread intended to indicate that the receiver is no + // longer valid. + success->value = false; + } + + // Delete temporary buffer allocated by other thread when the main thread is + // finished with it. + delete[] bytes; +} + +void MainThreadArchiveCallbackClient::ForwardClose( + SuccessPtr success_ptr, + ArchiveCallbackClient* client, + bool success) { + client->Close(success && success_ptr->value); +} + +} // namespace o3d diff --git a/o3d/import/cross/main_thread_archive_callback_client.h b/o3d/import/cross/main_thread_archive_callback_client.h new file mode 100644 index 0000000..7da39f7 --- /dev/null +++ b/o3d/import/cross/main_thread_archive_callback_client.h @@ -0,0 +1,85 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + +// MainThreadArchiveCallbackClient forwards data from one thread to +// another client that will run on the main thread. + +#ifndef O3D_IMPORT_CROSS_MAIN_THREAD_ARCHIVE_CALLBACK_CLIENT_H_ +#define O3D_IMPORT_CROSS_MAIN_THREAD_ARCHIVE_CALLBACK_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/thread.h" +#include "import/cross/archive_processor.h" +#include "import/cross/memory_stream.h" + +namespace o3d { + +class IMainThreadTaskPoster; +class ServiceLocator; + +class MainThreadArchiveCallbackClient : public ArchiveCallbackClient { + public: + MainThreadArchiveCallbackClient(ServiceLocator* service_locator, + ArchiveCallbackClient *receiver); + virtual ~MainThreadArchiveCallbackClient(); + + virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info); + virtual bool ReceiveFileData(MemoryReadStream *stream, size_t size); + virtual void Close(bool success); + + private: + struct Success : ::base::RefCountedThreadSafe< Success > { + bool value; + }; + + typedef scoped_refptr<Success> SuccessPtr; + + static void ForwardReceiveFileHeader(SuccessPtr success, + ArchiveCallbackClient* client, + ArchiveFileInfo file_info); + + static void ForwardReceiveFileData(SuccessPtr success, + ArchiveCallbackClient* client, + uint8* bytes, size_t size); + + static void ForwardClose(SuccessPtr success_ptr, + ArchiveCallbackClient* client, + bool success); + + IMainThreadTaskPoster* main_thread_task_poster_; + ArchiveCallbackClient* receiver_; + SuccessPtr success_; + + DISALLOW_COPY_AND_ASSIGN(MainThreadArchiveCallbackClient); +}; +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_MAIN_THREAD_ARCHIVE_CALLBACK_CLIENT_H_ diff --git a/o3d/import/cross/main_thread_archive_callback_client_test.cc b/o3d/import/cross/main_thread_archive_callback_client_test.cc new file mode 100644 index 0000000..ac3243a --- /dev/null +++ b/o3d/import/cross/main_thread_archive_callback_client_test.cc @@ -0,0 +1,219 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + + +// Tests functionality of the MainThreadArchiveCallbackClient class + +#include "tests/common/win/testing_common.h" +#include "base/task.h" +#include "core/cross/imain_thread_task_poster.h" +#include "core/cross/service_implementation.h" +#include "import/cross/main_thread_archive_callback_client.h" + +namespace o3d { + +// Simple standin for IMainThreadTaskPoster that invokes tasks asynchronously. +class TestMainThreadTaskPoster : IMainThreadTaskPoster { + public: + TestMainThreadTaskPoster(ServiceLocator* service_locator) + : service_(service_locator, this) { + } + + virtual bool IsSupported() { + return true; + } + + virtual void PostTask(Task* task) { + tasks_.push_back(task); + } + + void Flush() { + for (size_t i = 0; i < tasks_.size(); ++i) { + tasks_[i]->Run(); + delete tasks_[i]; + } + tasks_.clear(); + } + + private: + ServiceImplementation<IMainThreadTaskPoster> service_; + std::vector<Task*> tasks_; +}; + +class MockArchiveCallbackClient : public ArchiveCallbackClient { + public: + MockArchiveCallbackClient() + : receive_file_header_calls_(0), + receive_file_header_calls_file_info_("", 0), + receive_file_data_calls_(0), + receive_file_data_stream_(NULL), + receive_file_data_size_(0), + receive_file_data_result_(false), + close_calls_(0), + close_success_(false) { + } + + virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info) { + ++receive_file_header_calls_; + receive_file_header_calls_file_info_ = file_info; + } + + virtual bool ReceiveFileData(MemoryReadStream* stream, size_t size) { + ++receive_file_data_calls_; + receive_file_data_stream_.reset(new uint8[size]); + stream->Read(receive_file_data_stream_.get(), size); + receive_file_data_size_ = size; + return receive_file_data_result_; + } + + virtual void Close(bool success) { + ++close_calls_; + close_success_ = success; + } + + int receive_file_header_calls_; + ArchiveFileInfo receive_file_header_calls_file_info_; + + int receive_file_data_calls_; + scoped_array<uint8> receive_file_data_stream_; + size_t receive_file_data_size_; + bool receive_file_data_result_; + + int close_calls_; + bool close_success_; +}; + +class MainThreadArchiveCallbackClientTest : public testing::Test { + public: + MainThreadArchiveCallbackClientTest() + : main_thread_task_poster_(&service_locator_) { + } + + protected: + virtual void SetUp() { + receiver_client_ = new MockArchiveCallbackClient; + main_thread_client_ = new MainThreadArchiveCallbackClient( + &service_locator_, receiver_client_); + } + + virtual void TearDown() { + delete receiver_client_; + delete main_thread_client_; + } + + ServiceLocator service_locator_; + TestMainThreadTaskPoster main_thread_task_poster_; + MockArchiveCallbackClient* receiver_client_; + MainThreadArchiveCallbackClient* main_thread_client_; +}; + +TEST_F(MainThreadArchiveCallbackClientTest, + ReceiveFileHeaderForwardsToReceiver) { + ArchiveFileInfo info("hello", 7); + main_thread_client_->ReceiveFileHeader(info); + main_thread_task_poster_.Flush(); + + EXPECT_EQ(1, receiver_client_->receive_file_header_calls_); + EXPECT_EQ("hello", + receiver_client_->receive_file_header_calls_file_info_.GetFileName()); + EXPECT_EQ(7, + receiver_client_->receive_file_header_calls_file_info_.GetFileSize()); +} + +TEST_F(MainThreadArchiveCallbackClientTest, + ReceiveFileDataForwardsToReceiverAndReturnsTrueFirstTime) { + uint8 buffer[] = {1, 2, 3}; + MemoryReadStream stream(buffer, sizeof(buffer)); + receiver_client_->receive_file_data_result_ = true; + EXPECT_TRUE(main_thread_client_->ReceiveFileData(&stream, sizeof(buffer))); + main_thread_task_poster_.Flush(); + + EXPECT_EQ(1, receiver_client_->receive_file_data_calls_); + EXPECT_EQ(0, memcmp(receiver_client_->receive_file_data_stream_.get(), + buffer, sizeof(buffer))); + EXPECT_EQ(sizeof(buffer), receiver_client_->receive_file_data_size_); +} + +TEST_F(MainThreadArchiveCallbackClientTest, + ReceiveFileDataReportsFailureInSubsequentCall) { + receiver_client_->receive_file_data_result_ = true; + + uint8 buffer[] = {1, 2, 3, 4, 5, 6}; + MemoryReadStream stream(buffer, sizeof(buffer)); + + receiver_client_->receive_file_data_result_ = false; + EXPECT_TRUE(main_thread_client_->ReceiveFileData(&stream, 3)); + main_thread_task_poster_.Flush(); + EXPECT_EQ(1, receiver_client_->receive_file_data_calls_); + + + receiver_client_->receive_file_data_result_ = false; + EXPECT_FALSE(main_thread_client_->ReceiveFileData(&stream, 3)); + main_thread_task_poster_.Flush(); + EXPECT_EQ(1, receiver_client_->receive_file_data_calls_); +} + +TEST_F(MainThreadArchiveCallbackClientTest, + ReceiveFileHeaderForwardsUnsuccessfulClose) { + main_thread_client_->Close(false); + main_thread_task_poster_.Flush(); + + EXPECT_EQ(1, receiver_client_->close_calls_); + EXPECT_FALSE(receiver_client_->close_success_); +} + +TEST_F(MainThreadArchiveCallbackClientTest, + ReceiveFileHeaderForwardsSuccessfulClose) { + main_thread_client_->Close(true); + main_thread_task_poster_.Flush(); + + EXPECT_EQ(1, receiver_client_->close_calls_); + EXPECT_TRUE(receiver_client_->close_success_); +} + +TEST_F(MainThreadArchiveCallbackClientTest, + CloseForwardsFailureOfPreviousUnreportedCall) { + receiver_client_->receive_file_data_result_ = true; + + uint8 buffer[] = {1, 2, 3}; + MemoryReadStream stream(buffer, sizeof(buffer)); + + receiver_client_->receive_file_data_result_ = false; + EXPECT_TRUE(main_thread_client_->ReceiveFileData(&stream, sizeof(buffer))); + main_thread_task_poster_.Flush(); + EXPECT_EQ(1, receiver_client_->receive_file_data_calls_); + + main_thread_client_->Close(true); + main_thread_task_poster_.Flush(); + EXPECT_FALSE(receiver_client_->close_success_); +} + +} // namespace o3d diff --git a/o3d/import/cross/memory_stream.h b/o3d/import/cross/memory_stream.h index e71379a..2801b2b 100644 --- a/o3d/import/cross/memory_stream.h +++ b/o3d/import/cross/memory_stream.h @@ -266,9 +266,21 @@ class MemoryWriteStream { // Abstract interface to process a memory stream class StreamProcessor { public: + +// Status is defined in Linux +#ifdef Status +#undef Status +#endif + enum Status { + IN_PROGRESS, + SUCCESS, + FAILURE, + }; + virtual ~StreamProcessor() {} - virtual int ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process) = 0; + virtual Status ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) = 0; + virtual void Close(bool success) = 0; }; } // namespace o3d diff --git a/o3d/import/cross/tar_generator.cc b/o3d/import/cross/tar_generator.cc index ed72e05..cbfab24 100644 --- a/o3d/import/cross/tar_generator.cc +++ b/o3d/import/cross/tar_generator.cc @@ -234,8 +234,9 @@ void TarGenerator::FlushDataBuffer(bool flush_padding_zeroes) { } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -void TarGenerator::Finalize() { +void TarGenerator::Close(bool success) { FlushDataBuffer(true); + callback_client_->Close(success); } } // namespace o3d diff --git a/o3d/import/cross/tar_generator.h b/o3d/import/cross/tar_generator.h index 21bc733..7bb0ac4 100644 --- a/o3d/import/cross/tar_generator.h +++ b/o3d/import/cross/tar_generator.h @@ -50,21 +50,20 @@ #include <string> #include "base/basictypes.h" #include "core/cross/types.h" +#include "import/cross/iarchive_generator.h" #include "import/cross/memory_buffer.h" #include "import/cross/memory_stream.h" namespace o3d { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class TarGenerator { +class TarGenerator : public IArchiveGenerator { public: explicit TarGenerator(StreamProcessor *callback_client) : callback_client_(callback_client), data_block_buffer_(TAR_BLOCK_SIZE), // initialized to zeroes data_buffer_stream_(data_block_buffer_, TAR_BLOCK_SIZE) {} - virtual ~TarGenerator() { Finalize(); } - // Call AddFile() for each file entry, followed by calls to AddFileBytes() // for the file's data. Returns true on success. virtual bool AddFile(const String &file_name, size_t file_size); @@ -75,7 +74,7 @@ class TarGenerator { virtual int AddFileBytes(MemoryReadStream *stream, size_t n); // Must call this after all files and file data have been written - virtual void Finalize(); + virtual void Close(bool success); private: // Returns true on success. diff --git a/o3d/import/cross/tar_generator_test.cc b/o3d/import/cross/tar_generator_test.cc index 9403d62..0e6dccb 100644 --- a/o3d/import/cross/tar_generator_test.cc +++ b/o3d/import/cross/tar_generator_test.cc @@ -132,15 +132,30 @@ class CallbackClient : public StreamProcessor { : state_(VALIDATE_DIRECTORY_HEADER1), total_bytes_received_(0), memory_block_(kBlockSize), - write_stream_(memory_block_, kBlockSize) { + write_stream_(memory_block_, kBlockSize), + closed_(false), + success_(false) { } - virtual int ProcessBytes(MemoryReadStream *stream, + virtual Status ProcessBytes(MemoryReadStream *stream, size_t bytes_to_process); + virtual void Close(bool success) { + closed_ = true; + success_ = success; + } + size_t GetTotalBytesReceived() { return total_bytes_received_; } int GetState() { return state_; } + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + private: bool IsOctalDigit(uint8 c); bool IsOctalString(uint8 *p); @@ -159,11 +174,13 @@ class CallbackClient : public StreamProcessor { size_t total_bytes_received_; MemoryBuffer<uint8> memory_block_; MemoryWriteStream write_stream_; + bool closed_; + bool success_; }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -int CallbackClient::ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process) { +StreamProcessor::Status CallbackClient::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { total_bytes_received_ += bytes_to_process; while (bytes_to_process > 0) { @@ -254,7 +271,7 @@ int CallbackClient::ProcessBytes(MemoryReadStream *stream, bytes_to_process -= bytes_this_time; } - return 0; + return IN_PROGRESS; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -403,7 +420,7 @@ TEST_F(TarGeneratorTest, CreateSimpleArchive) { kFileLength3); generator.AddFileBytes(&file4_stream, kFileLength3); - generator.Finalize(); + generator.Close(true); // Verify that the tar byte stream produced is exactly divisible by // the block size @@ -412,6 +429,18 @@ TEST_F(TarGeneratorTest, CreateSimpleArchive) { // Make sure the state machine is in the expected state EXPECT_EQ(CallbackClient::FINISHED, client.GetState()); + + EXPECT_TRUE(client.closed()); + EXPECT_TRUE(client.success()); +} + +TEST_F(TarGeneratorTest, PassesThroughFailure) { + CallbackClient client; + TarGenerator generator(&client); + generator.Close(false); + + EXPECT_TRUE(client.closed()); + EXPECT_FALSE(client.success()); } } // namespace diff --git a/o3d/import/cross/tar_processor.cc b/o3d/import/cross/tar_processor.cc index 7b14c22..b4c268e 100644 --- a/o3d/import/cross/tar_processor.cc +++ b/o3d/import/cross/tar_processor.cc @@ -41,7 +41,8 @@ static const int kFileSizeOffset = 124; static const int kLinkFlagOffset = 156; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -int TarProcessor::ProcessBytes(MemoryReadStream *stream, size_t n) { +StreamProcessor::Status TarProcessor::ProcessBytes(MemoryReadStream *stream, + size_t n) { // Keep processing the byte-stream until we've consumed all we're given // size_t bytes_to_consume = n; @@ -56,7 +57,7 @@ int TarProcessor::ProcessBytes(MemoryReadStream *stream, size_t n) { stream->Read(reinterpret_cast<uint8*>(header_ + header_bytes_read_), bytes_to_read); if (bytes_read != bytes_to_read) { - return -1; + return FAILURE; } header_bytes_read_ += bytes_to_read; @@ -73,7 +74,7 @@ int TarProcessor::ProcessBytes(MemoryReadStream *stream, size_t n) { getting_filename_ = true; // We should pick some size that's too large. if (file_size > 1024) { - return -1; + return FAILURE; } } else { getting_filename_ = false; @@ -92,10 +93,10 @@ int TarProcessor::ProcessBytes(MemoryReadStream *stream, size_t n) { ArchiveFileInfo info(filename, file_size); callback_client_->ReceiveFileHeader(info); } else if (header_[0] == 0) { - // If filename is NULL due to zero-padding then file size - // should also be NULL - // TODO(gman): Won't this crash the plugin if I make a bad tar? - assert(file_size == 0); + // If filename is empty due to zero-padding then file size + // should also be zero. + if (file_size != 0) + return FAILURE; } } @@ -134,7 +135,7 @@ int TarProcessor::ProcessBytes(MemoryReadStream *stream, size_t n) { } else { if (!callback_client_->ReceiveFileData(&client_read_stream, client_bytes_this_time)) { - return -1; + return FAILURE; } } @@ -166,7 +167,11 @@ int TarProcessor::ProcessBytes(MemoryReadStream *stream, size_t n) { } } - return 0; + return IN_PROGRESS; +} + +void TarProcessor::Close(bool success) { + callback_client_->Close(success); } } // namespace o3d diff --git a/o3d/import/cross/tar_processor.h b/o3d/import/cross/tar_processor.h index 76976c3..66d4b4aa 100644 --- a/o3d/import/cross/tar_processor.h +++ b/o3d/import/cross/tar_processor.h @@ -66,7 +66,9 @@ class TarProcessor : public StreamProcessor { // Call to "push" bytes to be processed - the appropriate callback will get // called when we have enough data - virtual int ProcessBytes(MemoryReadStream *stream, size_t n); + virtual Status ProcessBytes(MemoryReadStream *stream, size_t n); + + virtual void Close(bool success); private: enum {TAR_HEADER_SIZE = 512}; diff --git a/o3d/import/cross/tar_processor_test.cc b/o3d/import/cross/tar_processor_test.cc index 1317042..77a2522 100644 --- a/o3d/import/cross/tar_processor_test.cc +++ b/o3d/import/cross/tar_processor_test.cc @@ -67,12 +67,27 @@ class TarTestClient : public ArchiveCallbackClient { virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info); virtual bool ReceiveFileData(MemoryReadStream *stream, size_t nbytes); + virtual void Close(bool success) { + closed_ = true; + success_ = success; + } + int GetFileCount() const { return file_count_; } size_t GetNumTotalBytesReceived() const { return index_; } + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + private: int file_count_; int index_; + bool closed_; + bool success_; }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -142,18 +157,31 @@ TEST_F(TarProcessorTest, LoadTarFile) { MemoryReadStream tar_stream(tar_data, file_size); size_t bytes_to_process = file_size; - int result = Z_OK; while (bytes_to_process > 0) { size_t bytes_this_time = bytes_to_process < kChunkSize ? bytes_to_process : kChunkSize; - result = tar_processor.ProcessBytes(&tar_stream, bytes_this_time); - EXPECT_TRUE(result == Z_OK); + StreamProcessor::Status status = tar_processor.ProcessBytes( + &tar_stream, bytes_this_time); + EXPECT_TRUE(status != StreamProcessor::FAILURE); bytes_to_process -= bytes_this_time; } + tar_processor.Close(true); + + EXPECT_TRUE(callback_client.closed()); + EXPECT_TRUE(callback_client.success()); free(tar_data); } +TEST_F(TarProcessorTest, PassesThroughFailure) { + TarTestClient callback_client; + TarProcessor tar_processor(&callback_client); + tar_processor.Close(false); + + EXPECT_TRUE(callback_client.closed()); + EXPECT_FALSE(callback_client.success()); +} + } // namespace diff --git a/o3d/import/cross/targz_generator.cc b/o3d/import/cross/targz_generator.cc index 02cb5b3..aa75712 100644 --- a/o3d/import/cross/targz_generator.cc +++ b/o3d/import/cross/targz_generator.cc @@ -37,12 +37,12 @@ namespace o3d { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TarGzGenerator::TarGzGenerator(StreamProcessor *callback_client) - : gz_compressor_(callback_client), tar_generator_(this) { + : gz_compressor_(callback_client), tar_generator_(&gz_compressor_) { } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -void TarGzGenerator::AddFile(const String &file_name, size_t file_size) { - tar_generator_.AddFile(file_name, file_size); +bool TarGzGenerator::AddFile(const String &file_name, size_t file_size) { + return tar_generator_.AddFile(file_name, file_size); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -57,16 +57,8 @@ int TarGzGenerator::AddFileBytes(const uint8 *data, size_t n) { } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -void TarGzGenerator::Finalize() { - tar_generator_.Finalize(); - gz_compressor_.Finalize(); -} - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -int TarGzGenerator::ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process) { - // pass bytestream from tar generator to the compressor - return gz_compressor_.ProcessBytes(stream, bytes_to_process); +void TarGzGenerator::Close(bool success) { + tar_generator_.Close(success); } } // namespace o3d diff --git a/o3d/import/cross/targz_generator.h b/o3d/import/cross/targz_generator.h index fd58c86..b8eb216 100644 --- a/o3d/import/cross/targz_generator.h +++ b/o3d/import/cross/targz_generator.h @@ -54,32 +54,26 @@ namespace o3d { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class TarGzGenerator : public StreamProcessor, public IArchiveGenerator { +class TarGzGenerator : public IArchiveGenerator { public: // |callback_client| receives the tar.gz byte stream explicit TarGzGenerator(StreamProcessor *callback_client); // Call AddFile() for each file entry, followed by calls to AddFileBytes() // for the file's data - void AddFile(const String &file_name, - size_t file_size); + virtual bool AddFile(const String &file_name, + size_t file_size); // Call with the file's data (after calling AddFile) // may be called one time with all the file's data, or multiple times // until all the data is provided - int AddFileBytes(MemoryReadStream *stream, size_t n); + virtual int AddFileBytes(MemoryReadStream *stream, size_t n); int AddFileBytes(const uint8 *data, size_t n); // Must call this after all files and file data have been written - virtual void Finalize(); + virtual void Close(bool success); private: - // StreamProcessor method: - // Receives the tar bytestream from the TarGenerator. - // It then feeds this into the GzCompressor - virtual int ProcessBytes(MemoryReadStream *stream, - size_t bytes_to_process); - GzCompressor gz_compressor_; TarGenerator tar_generator_; diff --git a/o3d/import/cross/targz_generator_test.cc b/o3d/import/cross/targz_generator_test.cc index 3104890..2e1cf7b 100644 --- a/o3d/import/cross/targz_generator_test.cc +++ b/o3d/import/cross/targz_generator_test.cc @@ -54,10 +54,12 @@ class TarGzTestClient : public StreamProcessor { public: explicit TarGzTestClient(size_t reference_size) : compressed_data_(reference_size), - write_stream_(compressed_data_, reference_size) { + write_stream_(compressed_data_, reference_size), + closed_(false), + success_(false) { }; - virtual int ProcessBytes(MemoryReadStream *stream, + virtual Status ProcessBytes(MemoryReadStream *stream, size_t bytes_to_process) { // Simply buffer up the tar.gz bytes // When we've gotten them all our Validate() method will be called @@ -69,7 +71,12 @@ class TarGzTestClient : public StreamProcessor { write_stream_.Write(p, bytes_to_process); - return 0; + return SUCCESS; + } + + virtual void Close(bool success) { + closed_ = true; + success_ = success; } // Checks that the data from the reference tar.gz file matches the tar.gz @@ -95,9 +102,19 @@ class TarGzTestClient : public StreamProcessor { } #endif + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + private: MemoryBuffer<uint8> compressed_data_; MemoryWriteStream write_stream_; + bool closed_; + bool success_; DISALLOW_COPY_AND_ASSIGN(TarGzTestClient); }; @@ -142,7 +159,7 @@ TEST_F(TarGzGeneratorTest, GenerateTarGz) { targz_generator.AddFile("test/shaders/BumpReflect.fx", shader_size); targz_generator.AddFileBytes(shader_data, shader_size); - targz_generator.Finalize(); + targz_generator.Close(true); #if defined(GENERATE_GOLDEN) std::string new_golden_file = *g_program_path + @@ -163,10 +180,21 @@ TEST_F(TarGzGeneratorTest, GenerateTarGz) { received_data, test_client.compressed_data_length())); + EXPECT_TRUE(test_client.closed()); + EXPECT_TRUE(test_client.success()); + free(targz_data); free(image_data); free(audio_data); free(shader_data); } +TEST_F(TarGzGeneratorTest, PassesThroughCloseFailure) { + TarGzTestClient test_client(1000); + TarGzGenerator targz_generator(&test_client); + targz_generator.Close(false); + + EXPECT_TRUE(test_client.closed()); + EXPECT_FALSE(test_client.success()); +} } // namespace o3d diff --git a/o3d/import/cross/targz_processor.cc b/o3d/import/cross/targz_processor.cc index a2a4ba8..5fb1275 100644 --- a/o3d/import/cross/targz_processor.cc +++ b/o3d/import/cross/targz_processor.cc @@ -57,10 +57,13 @@ TarGzProcessor::TarGzProcessor(ArchiveCallbackClient *callback_client) // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // -int TarGzProcessor::ProcessCompressedBytes(MemoryReadStream *stream, - size_t bytes_to_process) { - int result = gz_decompressor_.ProcessBytes(stream, bytes_to_process); - return result; +StreamProcessor::Status TarGzProcessor::ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process) { + return gz_decompressor_.ProcessBytes(stream, bytes_to_process); +} + +void TarGzProcessor::Close(bool success) { + gz_decompressor_.Close(success); } } // namespace o3d diff --git a/o3d/import/cross/targz_processor.h b/o3d/import/cross/targz_processor.h index ef83278..061d66e 100644 --- a/o3d/import/cross/targz_processor.h +++ b/o3d/import/cross/targz_processor.h @@ -46,12 +46,14 @@ namespace o3d { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class TarGzProcessor : public ArchiveProcessor { +class TarGzProcessor : public ArchiveProcessor { public: explicit TarGzProcessor(ArchiveCallbackClient *callback_client); - virtual int ProcessCompressedBytes(MemoryReadStream *stream, - size_t bytes_to_process); + virtual Status ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); + + virtual void Close(bool success); private: TarProcessor tar_processor_; diff --git a/o3d/import/cross/targz_processor_test.cc b/o3d/import/cross/targz_processor_test.cc index 5fc5914..a351ec8 100644 --- a/o3d/import/cross/targz_processor_test.cc +++ b/o3d/import/cross/targz_processor_test.cc @@ -56,17 +56,37 @@ const char *kConcatenatedContents = // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class ArchiveTestClient : public ArchiveCallbackClient { public: - explicit ArchiveTestClient() : file_count_(0), index_(0) {} + explicit ArchiveTestClient() + : file_count_(0), + index_(0), + closed_(false), + success_(false) {} + // ArchiveCallbackClient methods virtual void ReceiveFileHeader(const ArchiveFileInfo &file_info); virtual bool ReceiveFileData(MemoryReadStream *stream, size_t nbytes); + virtual void Close(bool success) { + closed_ = true; + success_ = success; + } + int GetFileCount() const { return file_count_; } size_t GetNumTotalBytesReceived() const { return index_; } + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + private: int file_count_; int index_; + bool closed_; + bool success_; }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -119,12 +139,17 @@ TEST_F(TarGzProcessorTest, LoadTarGzFile) { ArchiveTestClient test_callback_client; TarGzProcessor processor(&test_callback_client); - int result = processor.ProcessFile(filepath.c_str()); - EXPECT_EQ(Z_OK, result); + StreamProcessor::Status status = processor.ProcessFile(filepath.c_str()); + processor.Close(true); + + EXPECT_EQ(StreamProcessor::SUCCESS, status); EXPECT_EQ(3, test_callback_client.GetFileCount()); EXPECT_EQ(strlen(kConcatenatedContents), test_callback_client.GetNumTotalBytesReceived()); + + EXPECT_TRUE(test_callback_client.closed()); + EXPECT_TRUE(test_callback_client.success()); } // Tries to load something with a tar.gz extension, but which isn't @@ -136,8 +161,12 @@ TEST_F(TarGzProcessorTest, LoadBogusTarGzFile) { ArchiveTestClient test_callback_client; TarGzProcessor processor(&test_callback_client); - int result = processor.ProcessFile(filepath.c_str()); - EXPECT_TRUE(result != Z_OK); -} + StreamProcessor::Status status = processor.ProcessFile(filepath.c_str()); + processor.Close(false); + + EXPECT_EQ(StreamProcessor::FAILURE, status); + EXPECT_TRUE(test_callback_client.closed()); + EXPECT_FALSE(test_callback_client.success()); +} } // namespace diff --git a/o3d/import/cross/threaded_stream_processor.cc b/o3d/import/cross/threaded_stream_processor.cc new file mode 100644 index 0000000..c389c54 --- /dev/null +++ b/o3d/import/cross/threaded_stream_processor.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + +// ThreadedStreamProcessor forwards data from one thread to +// another processor that will run on a new thread owned by the +// ThreadedStreamProcessor. + +#include "import/cross/threaded_stream_processor.h" +#include "base/task.h" + +namespace o3d { + +ThreadedStreamProcessor::ThreadedStreamProcessor(StreamProcessor *receiver) + : receiver_(receiver), + thread_("ThreadedStreamProcessor"), + status_(IN_PROGRESS) { +} + +ThreadedStreamProcessor::~ThreadedStreamProcessor() { + // Wait for the other thread to stop so it does not access this object after + // it has been destroyed. + StopThread(); +} + +void ThreadedStreamProcessor::StartThread() { + if (!thread_.IsRunning()) { + thread_.Start(); + } +} + +void ThreadedStreamProcessor::StopThread() { + if (thread_.IsRunning()) { + thread_.Stop(); + } +} + +StreamProcessor::Status ThreadedStreamProcessor::ProcessBytes( + MemoryReadStream *stream, + size_t bytes_to_process) { + // Report any error on the other thread. + if (status_ == FAILURE) { + return status_; + } + + StartThread(); + + // Copy the bytes. They are deleted by the other thread when it has processed + // them. + uint8* copy = new uint8[bytes_to_process]; + stream->Read(copy, bytes_to_process); + + // Post a task to call ForwardBytes on the other thread. + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableFunction( + &ThreadedStreamProcessor::ForwardBytes, this, copy, bytes_to_process)); + + return IN_PROGRESS; +} + +void ThreadedStreamProcessor::Close(bool success) { + StartThread(); + + // Post a task to call ForwardClose on the other thread. + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableFunction( + &ThreadedStreamProcessor::ForwardClose, this, success)); +} + +void ThreadedStreamProcessor::ForwardBytes( + ThreadedStreamProcessor* processor, const uint8* bytes, size_t size) { + // Do not forward if an error has ocurred. Just delete the buffer. + if (processor->status_ == FAILURE) { + delete[] bytes; + return; + } + + // Pass bytes to the receiver. + MemoryReadStream stream(bytes, size); + processor->status_ = processor->receiver_->ProcessBytes(&stream, size); + + // Delete the buffer once the receiver is finished with them. + delete[] bytes; +} + +void ThreadedStreamProcessor::ForwardClose(ThreadedStreamProcessor* processor, + bool success) { + processor->receiver_->Close(success && (processor->status_ != FAILURE)); +} + +} // namespace o3d diff --git a/o3d/import/cross/threaded_stream_processor.h b/o3d/import/cross/threaded_stream_processor.h new file mode 100644 index 0000000..f040eb4 --- /dev/null +++ b/o3d/import/cross/threaded_stream_processor.h @@ -0,0 +1,72 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + +// ThreadedStreamProcessor forwards data from one thread to +// another processor that will run on a new thread owned by the +// ThreadedStreamProcessor. + +#ifndef O3D_IMPORT_CROSS_THREADED_STREAM_PROCESSOR_H_ +#define O3D_IMPORT_CROSS_THREADED_STREAM_PROCESSOR_H_ + +#include "base/basictypes.h" +#include "base/thread.h" +#include "import/cross/memory_stream.h" + +namespace o3d { + +class ThreadedStreamProcessor : public StreamProcessor { + public: + explicit ThreadedStreamProcessor(StreamProcessor *receiver); + virtual ~ThreadedStreamProcessor(); + + virtual Status ProcessBytes(MemoryReadStream *stream, + size_t bytes_to_process); + + virtual void Close(bool success); + + void StartThread(); + void StopThread(); + + private: + static void ForwardBytes(ThreadedStreamProcessor* processor, + const uint8* data, size_t size); + + static void ForwardClose(ThreadedStreamProcessor* processor, bool success); + + StreamProcessor* receiver_; + ::base::Thread thread_; + Status status_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedStreamProcessor); +}; +} // namespace o3d + +#endif // O3D_IMPORT_CROSS_THREADED_STREAM_PROCESSOR_H_ diff --git a/o3d/import/cross/threaded_stream_processor_test.cc b/o3d/import/cross/threaded_stream_processor_test.cc new file mode 100644 index 0000000..44698ad3 --- /dev/null +++ b/o3d/import/cross/threaded_stream_processor_test.cc @@ -0,0 +1,155 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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 "import/cross/memory_buffer.h" +#include "import/cross/threaded_stream_processor.h" +#include "tests/common/win/testing_common.h" +#include "tests/common/cross/test_utils.h" + +using test_utils::ReadFile; + +namespace o3d { + +class ThreadedStreamProcessorTest : public testing::Test { + protected: +}; + +class TestReceiver : public StreamProcessor { + public: + explicit TestReceiver(size_t size) : closed_(false), success_(false) { + buffer_.Allocate(size); + stream_.Assign(buffer_, size); + } + + virtual Status ProcessBytes(MemoryReadStream *input_stream, + size_t bytes_to_process) { + const uint8 *p = input_stream->GetDirectMemoryPointer(); + input_stream->Skip(bytes_to_process); + + size_t bytes_written = stream_.Write(p, bytes_to_process); + EXPECT_EQ(bytes_written, bytes_to_process); + + return SUCCESS; + } + + virtual void Close(bool success) { + closed_ = true; + success_ = success; + } + + uint8 *GetResultBuffer() { return buffer_; } + size_t GetResultLength() const { return stream_.GetStreamPosition(); } + + bool closed() const { + return closed_; + } + + bool success() const { + return success_; + } + + private: + MemoryBuffer<uint8> buffer_; + MemoryWriteStream stream_; + bool closed_; + bool success_; +}; + +TEST_F(ThreadedStreamProcessorTest, CanForwardZeroBuffers) { + TestReceiver receiver(1000); + ThreadedStreamProcessor processor(&receiver); + processor.Close(true); + processor.StopThread(); + + EXPECT_EQ(0, receiver.GetResultLength()); + EXPECT_TRUE(receiver.closed()); + EXPECT_TRUE(receiver.success()); +} + +TEST_F(ThreadedStreamProcessorTest, CanForwardOneBufferOfZeroBytes) { + uint8 buffer[5] = { 1, 2, 3, 4, 5 }; + TestReceiver receiver(1000); + + ThreadedStreamProcessor processor(&receiver); + MemoryReadStream stream(buffer, sizeof(buffer)); + processor.ProcessBytes(&stream, 0); + processor.Close(true); + processor.StopThread(); + + EXPECT_EQ(0, receiver.GetResultLength()); + EXPECT_TRUE(receiver.closed()); + EXPECT_TRUE(receiver.success()); +} + +TEST_F(ThreadedStreamProcessorTest, CanForwardOneBufferFullOfBytes) { + uint8 buffer[5] = { 1, 2, 3, 4, 5 }; + TestReceiver receiver(1000); + + ThreadedStreamProcessor processor(&receiver); + MemoryReadStream stream(buffer, sizeof(buffer)); + processor.ProcessBytes(&stream, sizeof(buffer)); + processor.Close(true); + processor.StopThread(); + + EXPECT_EQ(sizeof(buffer), receiver.GetResultLength()); + EXPECT_EQ(0, memcmp(buffer, receiver.GetResultBuffer(), sizeof(buffer))); + EXPECT_TRUE(receiver.closed()); + EXPECT_TRUE(receiver.success()); +} + +TEST_F(ThreadedStreamProcessorTest, CanForwardTwoBuffersFullOfBytes) { + uint8 buffer[5] = { 1, 2, 3, 4, 5 }; + TestReceiver receiver(1000); + + ThreadedStreamProcessor processor(&receiver); + MemoryReadStream stream(buffer, sizeof(buffer)); + processor.ProcessBytes(&stream, 2); + processor.ProcessBytes(&stream, 3); + processor.Close(true); + processor.StopThread(); + + EXPECT_EQ(sizeof(buffer), receiver.GetResultLength()); + EXPECT_EQ(0, memcmp(buffer, receiver.GetResultBuffer(), sizeof(buffer))); + EXPECT_TRUE(receiver.closed()); + EXPECT_TRUE(receiver.success()); +} + +TEST_F(ThreadedStreamProcessorTest, PassesThroughStreamFailed) { + TestReceiver receiver(1000); + ThreadedStreamProcessor processor(&receiver); + processor.Close(false); + processor.StopThread(); + + EXPECT_TRUE(receiver.closed()); + EXPECT_FALSE(receiver.success()); +} + +} // namespace o3d diff --git a/o3d/libevent/build.scons b/o3d/libevent/build.scons new file mode 100644 index 0000000..63ef0ad --- /dev/null +++ b/o3d/libevent/build.scons @@ -0,0 +1,88 @@ +# Copyright 2009, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "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 COPYRIGHT +# OWNER OR CONTRIBUTORS 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. +
+# This SConscript is currently only suitable for Linux.
+
+Import('env')
+
+env.Prepend( + CPPPATH = [ + '$LIBEVENT_DIR', + ], +) + +env.Append(
+ CPPDEFINES = [
+ 'HAVE_CONFIG_H',
+ ],
+)
+
+if env.Bit('mac'):
+ env.Prepend(
+ CPPPATH = [
+ '$LIBEVENT_DIR/mac',
+ ],
+ )
+
+if env.Bit('linux'):
+ env.Prepend(
+ CPPPATH = [
+ '$LIBEVENT_DIR/linux',
+ ],
+ )
+
+inputs = [
+ 'buffer',
+ 'evbuffer',
+ 'evdns',
+ 'event',
+ 'event_tagging',
+ 'evrpc',
+ 'evutil',
+ 'http',
+ 'log',
+ 'poll',
+ 'select',
+ 'signal',
+ 'strlcpy',
+]
+
+if env.Bit('mac'):
+ inputs.extend([
+ 'kqueue',
+ ])
+
+if env.Bit('linux'):
+ inputs.extend([
+ 'epoll',
+ 'epoll_sub',
+ ])
+
+objects = env.MakeObjects(inputs, '$LIBEVENT_DIR', 'c') +env.ComponentLibrary('event', inputs) diff --git a/o3d/main.scons b/o3d/main.scons index 27c52f8..ddfd819 100644 --- a/o3d/main.scons +++ b/o3d/main.scons @@ -609,6 +609,7 @@ mac_env.Append(CPPDEFINES = ['RENDERER_GL', '-mmacosx-version-min=10.4' ], BUILD_COMPONENTS = [ + 'event', 'statsreport', 'statsreport/common', ], @@ -689,7 +690,8 @@ linux_env.Append( LIBS = ['pthread', 'rt', 'dl'], NACL_HTP_LIBS = ['ssl', 'crypto'], BUILD_COMPONENTS = [ - 'installer/linux' + 'installer/linux', + 'event', ], ) diff --git a/o3d/plugin/build.scons b/o3d/plugin/build.scons index 6449232..53d4dcf 100644 --- a/o3d/plugin/build.scons +++ b/o3d/plugin/build.scons @@ -130,6 +130,7 @@ if env.Bit('mac'): LIBS = [ 'o3dStatsreport_Common', 'o3dStatsreport', + 'event', logging_lib, ], FRAMEWORKS = [ @@ -150,7 +151,12 @@ if env.Bit('mac'): ) if env.Bit('linux'): - env.Append(CPPDEFINES = ['XP_UNIX', 'MOZ_X11']); + env.Append( + CPPDEFINES = ['XP_UNIX', 'MOZ_X11'], + LIBS = [ + 'event', + ], + ); # Add libraries based on the requested renderer env.Append(CPPPATH = env['RENDERER_INCLUDE_PATH'], @@ -183,6 +189,7 @@ inputs = AUTOGEN_CC_FILES + [ 'cross/async_loading.cc', 'cross/archive_request_static_glue.cc', 'cross/blacklist.cc', + 'cross/main_thread_task_poster.cc', 'cross/o3d_glue.cc', 'cross/np_v8_bridge.cc', 'cross/out_of_memory.cc', diff --git a/o3d/plugin/cross/main_thread_task_poster.cc b/o3d/plugin/cross/main_thread_task_poster.cc new file mode 100644 index 0000000..24a42e2 --- /dev/null +++ b/o3d/plugin/cross/main_thread_task_poster.cc @@ -0,0 +1,64 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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 <npapi.h> +#include "plugin/cross/main_thread_task_poster.h" + +namespace o3d { + +MainThreadTaskPoster::MainThreadTaskPoster(ServiceLocator* service_locator, + NPP npp) + : service_(service_locator, this), + npp_(npp) { +} + +MainThreadTaskPoster::~MainThreadTaskPoster() { +} + +bool MainThreadTaskPoster::IsSupported() { + int plugin_major, plugin_minor, browser_major, browser_minor; + NPN_Version(&plugin_major, &plugin_minor, &browser_major, &browser_minor); + return browser_major > 0 || + browser_minor >= NPVERS_HAS_PLUGIN_THREAD_ASYNC_CALL; +} + +void MainThreadTaskPoster::PostTask(Task* task) { + DCHECK(IsSupported()); + NPN_PluginThreadAsyncCall(npp_, &MainThreadTaskPoster::RunTask, task); +} + +void MainThreadTaskPoster::RunTask(void* data) { + Task* task = static_cast<Task*>(data); + task->Run(); + delete task; +} +} // namespace o3d diff --git a/o3d/plugin/cross/main_thread_task_poster.h b/o3d/plugin/cross/main_thread_task_poster.h new file mode 100644 index 0000000..e322af1 --- /dev/null +++ b/o3d/plugin/cross/main_thread_task_poster.h @@ -0,0 +1,57 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + + +#ifndef O3D_PLUGIN_CROSS_MAIN_THREAD_TASK_POSTER_H +#define O3D_PLUGIN_CROSS_MAIN_THREAD_TASK_POSTER_H + +#include <npapi.h> +#include "core/cross/imain_thread_task_poster.h" +#include "core/cross/service_implementation.h" + +namespace o3d { + +class MainThreadTaskPoster : public IMainThreadTaskPoster { + public: + MainThreadTaskPoster(ServiceLocator* service_locator, NPP npp); + virtual ~MainThreadTaskPoster(); + virtual bool IsSupported(); + virtual void PostTask(Task* task); + private: + static void RunTask(void* data); + ServiceImplementation<IMainThreadTaskPoster> service_; + NPP npp_; + DISALLOW_COPY_AND_ASSIGN(MainThreadTaskPoster); +}; + +} // namespace o3d + +#endif // O3D_PLUGIN_CROSS_MAIN_THREAD_TASK_POSTER_H diff --git a/o3d/plugin/cross/o3d_glue.cc b/o3d/plugin/cross/o3d_glue.cc index 9d6c24e..9053255 100644 --- a/o3d/plugin/cross/o3d_glue.cc +++ b/o3d/plugin/cross/o3d_glue.cc @@ -101,6 +101,7 @@ PluginObject::PluginObject(NPP npp) client_info_manager_(&service_locator_), object_manager_(&service_locator_), profiler_(&service_locator_), + main_thread_task_poster_(&service_locator_, npp), fullscreen_(false), renderer_(NULL), features_(NULL), @@ -822,52 +823,52 @@ void PluginObject::PlatformSpecificSetCursor() { #endif // OS_LINUX -void PluginObject::AsyncTick() { - if (pending_ticks_ >= 1) +namespace { +void TickPluginObject(void* data) { + PluginObject* plugin_object = static_cast<PluginObject*>(data); + + // Check the plugin has not been destroyed already. Chrome sometimes invokes + // async callbacks after destruction. + if (!plugin_object->client()) return; - class TickCallback : public StreamManager::FinishedCallback { - public: - explicit TickCallback(PluginObject* plugin_object) - : plugin_object_(plugin_object) { - } + // Don't allow reentrancy through asynchronous ticks. Chrome sometimes does + // this. It is also possible for the asyncronous call to be invoked while + // a message is being handled. This prevents that. + Client::ScopedIncrement reentrance_count(plugin_object->client()); + if (reentrance_count.get() > 1) + return; - virtual void Run(DownloadStream*, - bool, - const std::string&, - const std::string&) { - plugin_object_->Tick(); - } + plugin_object->Tick(); +} +} - private: - PluginObject* plugin_object_; - }; +void PluginObject::AsyncTick() { + if (pending_ticks_ >= 1) + return; ++pending_ticks_; - // Invoke Client::Tick and Client::RenderClient in a way that is asynchronous - // in Chrome. This avoids issues with making calls into the browser from a - // message handler. - // If NPN_PluginThreadAsyncCall worked in more browsers, it would be simpler - // to use that. - // We're calling LoadURL here with a URL that will return 0 bytes on browsers - // that support the "data:" protocol and fail in browsers that don't like IE. - // On browsers that support it, the side effect is to call the TickCallback. - if (!stream_manager_->LoadURL("data:,", NULL, NULL, NULL, - new TickCallback(this), NP_NORMAL)) { - // Fallback on synchronous call if asynchronous load fails. + // Invoke Tick asynchronously if NPN_PluginThreadAsyncCall is supported. + // Otherwise invoke it synchronously. + int plugin_major, plugin_minor, browser_major, browser_minor; + NPN_Version(&plugin_major, &plugin_minor, &browser_major, &browser_minor); + if (browser_major > 0 || + browser_minor >= NPVERS_HAS_PLUGIN_THREAD_ASYNC_CALL) { + NPN_PluginThreadAsyncCall(npp_, TickPluginObject, this); + } else { Tick(); } } void PluginObject::Tick() { + DCHECK(pending_ticks_ > 0); + --pending_ticks_; + client_->Tick(); if (renderer_ && renderer_->need_to_render()) { client_->RenderClient(true); } - - DCHECK(pending_ticks_ > 0); - --pending_ticks_; } } // namespace _o3d diff --git a/o3d/plugin/cross/o3d_glue.h b/o3d/plugin/cross/o3d_glue.h index c61d1d9..c318ea5 100644 --- a/o3d/plugin/cross/o3d_glue.h +++ b/o3d/plugin/cross/o3d_glue.h @@ -64,6 +64,7 @@ #include "core/cross/object_manager.h" #include "core/cross/error.h" #include "core/cross/profiler.h" +#include "plugin/cross/main_thread_task_poster.h" #include "plugin/cross/np_v8_bridge.h" #include "client_glue.h" #include "third_party/nixysa/static_glue/npapi/common.h" @@ -85,6 +86,7 @@ using o3d::ClientInfoManager; using o3d::EvaluationCounter; using o3d::Features; using o3d::EvaluationCounter; +using o3d::MainThreadTaskPoster; using o3d::ObjectManager; using o3d::Profiler; using o3d::Renderer; @@ -129,6 +131,7 @@ class PluginObject: public NPObject { ClientInfoManager client_info_manager_; ObjectManager object_manager_; Profiler profiler_; + MainThreadTaskPoster main_thread_task_poster_; bool fullscreen_; // Are we rendered fullscreen or in the plugin region? Renderer *renderer_; Client *client_; diff --git a/o3d/plugin/idl/archive_request.idl b/o3d/plugin/idl/archive_request.idl index 1633a46..ef87f3e 100644 --- a/o3d/plugin/idl/archive_request.idl +++ b/o3d/plugin/idl/archive_request.idl @@ -31,8 +31,11 @@ namespace o3d { +[include="import/cross/archive_request.h", async] +callback void ArchiveReadyStateChangeCallback(); + [include="import/cross/archive_request.h"] -callback void ArchiveRequestCallback(); +callback void ArchiveFileAvailableCallback(RawData rawData); %[ An ArchiveRequest object is used to carry out an asynchronous request for a @@ -87,14 +90,14 @@ callback void ArchiveRequestCallback(); A callback that gets called each time readyState changes. %] [setter] - ArchiveRequestCallback? onreadystatechange; + ArchiveReadyStateChangeCallback? onreadystatechange; %[ A callback that gets called each time a file fully downloads and becomes available. %] [setter] - ArchiveRequestCallback? onfileavailable; + ArchiveFileAvailableCallback? onfileavailable; %[ The uri of the archive being downloaded. @@ -104,6 +107,8 @@ callback void ArchiveRequestCallback(); %[ A RawData object representing the file that is currently available. Note: This value is only valid inside the onfileavailable callback. + Note: This property is deprecated. It is now an argument of the + onfileavailable callback. %] [getter] RawData? data; diff --git a/o3d/plugin/idl/file_request.idl b/o3d/plugin/idl/file_request.idl index 5b1c9a3..0e64812 100644 --- a/o3d/plugin/idl/file_request.idl +++ b/o3d/plugin/idl/file_request.idl @@ -31,7 +31,8 @@ namespace o3d { -[include="core/cross/file_request.h"] callback void FileRequestCallback(); +[include="core/cross/file_request.h", async] +callback void FileRequestCallback(); %[ A FileRequest object is used to carry out an asynchronous request for a file diff --git a/o3d/plugin/linux/main_linux.cc b/o3d/plugin/linux/main_linux.cc index b91c9d8..5fe7439 100644 --- a/o3d/plugin/linux/main_linux.cc +++ b/o3d/plugin/linux/main_linux.cc @@ -50,9 +50,12 @@ using o3d::Event; namespace { // We would normally make this a stack variable in main(), but in a -// plugin, that's not possible, so we allocate it dynamically and -// destroy it explicitly. -scoped_ptr<base::AtExitManager> g_at_exit_manager; +// plugin, that's not possible, so we make it a global. When the DLL is loaded +// this it gets constructed and when it is unlooaded it is destructed. Note +// that this cannot be done in NP_Initialize and NP_Shutdown because those +// calls do not necessarily signify the DLL being loaded and unloaded. If the +// DLL is not unloaded then the values of global variables are preserved. +base::AtExitManager g_at_exit_manager; bool g_xembed_support = false; @@ -584,10 +587,6 @@ NPError InitializePlugin() { if (!o3d::SetupOutOfMemoryHandler()) return NPERR_MODULE_LOAD_FAILED_ERROR; - // Initialize the AtExitManager so that base singletons can be - // destroyed properly. - g_at_exit_manager.reset(new base::AtExitManager()); - CommandLine::Init(0, NULL); InitLogging("debug.log", logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG, @@ -636,9 +635,6 @@ NPError EXPORT_SYMBOL OSCALL NP_Shutdown(void) { CommandLine::Terminate(); - // Force all base singletons to be destroyed. - g_at_exit_manager.reset(NULL); - return NPERR_NO_ERROR; } diff --git a/o3d/plugin/mac/main_mac.mm b/o3d/plugin/mac/main_mac.mm index 11355b7..ac47bb0 100644 --- a/o3d/plugin/mac/main_mac.mm +++ b/o3d/plugin/mac/main_mac.mm @@ -66,9 +66,12 @@ using o3d::Event; namespace { // We would normally make this a stack variable in main(), but in a -// plugin, that's not possible, so we allocate it dynamically and -// destroy it explicitly. -scoped_ptr<base::AtExitManager> g_at_exit_manager; +// plugin, that's not possible, so we make it a global. When the DLL is loaded +// this it gets constructed and when it is unlooaded it is destructed. Note +// that this cannot be done in NP_Initialize and NP_Shutdown because those +// calls do not necessarily signify the DLL being loaded and unloaded. If the +// DLL is not unloaded then the values of global variables are preserved. +base::AtExitManager g_at_exit_manager; #define CFTIMER // #define DEFERRED_DRAW_ON_NULLEVENTS @@ -500,10 +503,6 @@ NPError InitializePlugin() { o3d::gRenderTimer.Start(); #endif // CFTIMER - // Initialize the AtExitManager so that base singletons can be - // destroyed properly. - g_at_exit_manager.reset(new base::AtExitManager()); - // Turn on the logging. CommandLine::Init(0, NULL); InitLogging("debug.log", @@ -691,9 +690,6 @@ NPError OSCALL NP_Shutdown(void) { o3d::gRenderTimer.Stop(); #endif - // Force all base singletons to be destroyed. - g_at_exit_manager.reset(NULL); - o3d::ShutdownBreakpad(); #endif // O3D_INTERNAL_PLUGIN diff --git a/o3d/plugin/npapi_host_control/win/host_control.cc b/o3d/plugin/npapi_host_control/win/host_control.cc index 117245c..957f465 100644 --- a/o3d/plugin/npapi_host_control/win/host_control.cc +++ b/o3d/plugin/npapi_host_control/win/host_control.cc @@ -260,6 +260,17 @@ LRESULT CHostControl::OnDestroy(UINT uMsg, return 0; } +LRESULT CHostControl::OnPluginAsyncCall(UINT message, + WPARAM w_param, + LPARAM l_param, + BOOL& handled) { + typedef void (*Function)(void*); + Function function = reinterpret_cast<Function>(w_param); + void* data = reinterpret_cast<void*>(l_param); + function(data); + handled = TRUE; + return 0; +} HRESULT CHostControl::FinalConstruct() { return ConstructPluginProxy(); diff --git a/o3d/plugin/npapi_host_control/win/host_control.h b/o3d/plugin/npapi_host_control/win/host_control.h index 5cf50ad..ae37487 100644 --- a/o3d/plugin/npapi_host_control/win/host_control.h +++ b/o3d/plugin/npapi_host_control/win/host_control.h @@ -58,6 +58,8 @@ class NPPluginProxy; +const UINT WM_PLUGINASYNCCALL = WM_USER + 100; + // Class implementing an ActiveX control for containing NPAPI plugin-objects. // This needs to be CComMultiThreadModel because these objects are concurrently // AddRefed and Released from StreamOperation threads. @@ -110,6 +112,7 @@ DECLARE_REGISTRY_RESOURCEID(IDR_HOSTCONTROL) BEGIN_MSG_MAP(CHostControl) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + MESSAGE_HANDLER(WM_PLUGINASYNCCALL, OnPluginAsyncCall) END_MSG_MAP() BEGIN_CONNECTION_POINT_MAP(CHostControl) @@ -176,6 +179,8 @@ END_PROP_MAP() LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnPluginAsyncCall(UINT message, WPARAM w_param, LPARAM l_param, + BOOL& handled); // Initiates a data transfer, calling back into the hosted plug-in instance // on status updates. Does not block on the transfer. diff --git a/o3d/plugin/npapi_host_control/win/np_browser_proxy.cc b/o3d/plugin/npapi_host_control/win/np_browser_proxy.cc index 968e71e..bba80d1 100644 --- a/o3d/plugin/npapi_host_control/win/np_browser_proxy.cc +++ b/o3d/plugin/npapi_host_control/win/np_browser_proxy.cc @@ -63,7 +63,7 @@ NPError OpenURL(NPBrowserProxy* browser_proxy, NPNetscapeFuncs NPBrowserProxy::kNetscapeFunctions = { sizeof(kNetscapeFunctions), - NPVERS_HAS_NPOBJECT_ENUM, + NPVERS_HAS_PLUGIN_THREAD_ASYNC_CALL, NPN_GetURL, NPN_PostURL, NPN_RequestRead, @@ -107,6 +107,7 @@ NPNetscapeFuncs NPBrowserProxy::kNetscapeFunctions = { NULL, NULL, NPN_Enumerate, + NPN_PluginThreadAsyncCall, }; NPBrowserProxy::NPBrowserProxy(CHostControl* host, IDispatchEx* window_dispatch) @@ -790,6 +791,22 @@ bool NPBrowserProxy::NPN_Evaluate(NPP npp, return success; } +void NPBrowserProxy::NPN_PluginThreadAsyncCall(NPP npp, + void (*function)(void *), + void *data) { + if (!npp || !function) { + return; + } + + NPBrowserProxy *browser_proxy = static_cast<NPBrowserProxy*>(npp->ndata); + CHostControl *host_control = browser_proxy->GetHostingControl(); + ATLASSERT(host_control); + + host_control->PostMessage(WM_PLUGINASYNCCALL, + reinterpret_cast<WPARAM>(function), + reinterpret_cast<LPARAM>(data)); +} + void NPBrowserProxy::NPN_SetException(NPObject *obj, const NPUTF8 *message) { ATLASSERT(false && "NPN_SetException not implemented"); diff --git a/o3d/plugin/npapi_host_control/win/np_browser_proxy.h b/o3d/plugin/npapi_host_control/win/np_browser_proxy.h index e245c84..4c28cea 100644 --- a/o3d/plugin/npapi_host_control/win/np_browser_proxy.h +++ b/o3d/plugin/npapi_host_control/win/np_browser_proxy.h @@ -266,6 +266,10 @@ class NPBrowserProxy { NPString *script, NPVariant *result); + static void NPN_PluginThreadAsyncCall(NPP npp, + void (*function)(void *), + void *data); + static void NPN_SetException(NPObject *obj, const NPUTF8 *message); // Static table of function pointers to the member function entry points diff --git a/o3d/plugin/plugin.gyp b/o3d/plugin/plugin.gyp index 22a881e..f241187 100644 --- a/o3d/plugin/plugin.gyp +++ b/o3d/plugin/plugin.gyp @@ -50,6 +50,8 @@ 'cross/download_stream.h', 'cross/main.cc', 'cross/main.h', + 'cross/main_thread_task_poster.cc', + 'cross/main_thread_task_poster.h', 'cross/marshaling_utils.h', 'cross/np_v8_bridge.cc', 'cross/np_v8_bridge.h', diff --git a/o3d/plugin/win/main_win.cc b/o3d/plugin/win/main_win.cc index 69cd647..f2feeae 100644 --- a/o3d/plugin/win/main_win.cc +++ b/o3d/plugin/win/main_win.cc @@ -90,9 +90,12 @@ namespace { const wchar_t* const kFullScreenWindowClassName = L"O3DFullScreenWindowClass"; // We would normally make this a stack variable in main(), but in a -// plugin, that's not possible, so we allocate it dynamically and -// destroy it explicitly. -scoped_ptr<base::AtExitManager> g_at_exit_manager; +// plugin, that's not possible, so we make it a global. When the DLL is loaded +// this it gets constructed and when it is unlooaded it is destructed. Note +// that this cannot be done in NP_Initialize and NP_Shutdown because those +// calls do not necessarily signify the DLL being loaded and unloaded. If the +// DLL is not unloaded then the values of global variables are preserved. +base::AtExitManager g_at_exit_manager; static int HandleKeyboardEvent(PluginObject *obj, HWND hWnd, @@ -494,13 +497,6 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { switch (Msg) { case WM_PAINT: { - if (reentrance_count.get() > 1) { - // In Chrome, alert dialogs raised from JavaScript cause - // reentrant WM_PAINT messages to be dispatched and 100% CPU - // to be consumed unless we call this - ::ValidateRect(hWnd, NULL); - break; // Ignore this message; we're reentrant. - } PAINTSTRUCT paint_struct; HDC hdc = ::BeginPaint(hWnd, &paint_struct); if (paint_struct.rcPaint.right - paint_struct.rcPaint.left != 0 || @@ -564,7 +560,6 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { // repaint the window. if (obj->client()->render_mode() == o3d::Client::RENDERMODE_CONTINUOUS) { InvalidateRect(obj->GetHWnd(), NULL, FALSE); - reentrance_count.decrement(); UpdateWindow(obj->GetHWnd()); } @@ -691,10 +686,6 @@ NPError InitializePlugin() { g_exception_manager->StartMonitoring(); } - // Initialize the AtExitManager so that base singletons can be - // destroyed properly. - g_at_exit_manager.reset(new base::AtExitManager()); - // Turn on the logging. CommandLine::Init(0, NULL); InitLogging(L"debug.log", @@ -791,9 +782,6 @@ NPError OSCALL NP_Shutdown(void) { CommandLine::Terminate(); - // Force all base singletons to be destroyed. - g_at_exit_manager.reset(NULL); - // TODO : This is commented out until we can determine if // it's safe to shutdown breakpad at this stage (Gears, for // example, never deletes...) @@ -891,10 +879,10 @@ NPError NPP_SetWindow(NPP instance, NPWindow *window) { DCHECK(obj->GetPluginHWnd()); DCHECK(obj->GetFullscreenHWnd()); DCHECK(obj->GetPluginHWnd() == hWnd); - + // Exit full screen if the plugin window is being modified. obj->CancelFullscreenDisplay(); - + return NPERR_NO_ERROR; } DCHECK(!obj->GetPluginHWnd()); diff --git a/o3d/samples/animated-scene.html b/o3d/samples/animated-scene.html index b60f286..c9e4b72 100644 --- a/o3d/samples/animated-scene.html +++ b/o3d/samples/animated-scene.html @@ -60,6 +60,7 @@ o3djs.require('o3djs.scene'); // Events // init() once the page has finished loading. window.onload = init; +window.onunload = unload; // global variables var g_o3d; diff --git a/o3d/samples/o3djs/io.js b/o3d/samples/o3djs/io.js index 2bd1a3a..690bd2c 100644 --- a/o3d/samples/o3djs/io.js +++ b/o3d/samples/o3djs/io.js @@ -494,9 +494,7 @@ o3djs.io.loadArchiveAdvanced = function(pack, var request = pack.createArchiveRequest(); var loadInfo = o3djs.io.createLoadInfo(request, true); request.open('GET', url); - request.onfileavailable = function() { - onFileAvailable(/** @type {!o3d.RawData} */ (request.data)); - }; + request.onfileavailable = onFileAvailable; /** * @ignore */ diff --git a/o3d/serializer/cross/serializer_test.cc b/o3d/serializer/cross/serializer_test.cc index 4ab5abe..aa19465 100644 --- a/o3d/serializer/cross/serializer_test.cc +++ b/o3d/serializer/cross/serializer_test.cc @@ -68,12 +68,16 @@ struct AddFileRecord { class MockArchiveGenerator : public IArchiveGenerator { public: - virtual void AddFile(const String& file_name, + MockArchiveGenerator() : closed_(false), success_(false) { + } + + virtual bool AddFile(const String& file_name, size_t file_size) { AddFileRecord record; record.file_name_ = file_name; record.file_size_ = file_size; add_file_records_.push_back(record); + return true; } virtual int AddFileBytes(MemoryReadStream* stream, size_t numBytes) { @@ -85,7 +89,14 @@ class MockArchiveGenerator : public IArchiveGenerator { return 0; } + virtual void Close(bool success) { + closed_ = true; + success_ = success; + } + vector<AddFileRecord> add_file_records_; + bool closed_; + bool success_; }; class SerializerTest : public testing::Test { @@ -323,9 +334,10 @@ TEST_F(SerializerTest, ShouldSerializeCurveKeysToSingleBinaryFile) { serializer_.SerializePack(pack_); EXPECT_EQ(1, archive_generator_.add_file_records_.size()); const AddFileRecord& record = archive_generator_.add_file_records_[0]; - EXPECT_EQ("curve-keys.bin", record.file_name_); + EXPECT_FALSE(archive_generator_.closed_); + // Test that the data matches what we get if we call SerializeCurve directly // The file should contain the concatenated contents of both curves MemoryBuffer<uint8> contents1; @@ -414,6 +426,8 @@ TEST_F(SerializerTest, SerializesAllIndexBufferBinaryToSingleFileInArchive) { const AddFileRecord& record = archive_generator_.add_file_records_[0]; EXPECT_EQ("index-buffers.bin", record.file_name_); + EXPECT_FALSE(archive_generator_.closed_); + // Test that the data matches what we get if we call SerializeBuffer directly // The file should contain the concatenated contents of both buffers MemoryBuffer<uint8> contents1; @@ -743,6 +757,8 @@ TEST_F(SerializerTest, ShouldSerializeSkinToSingleBinaryFile) { const AddFileRecord& record = archive_generator_.add_file_records_[0]; EXPECT_EQ("skins.bin", record.file_name_); + EXPECT_FALSE(archive_generator_.closed_); + // Test that the data matches what we get if we call SerializeSkin directly // The file should contain the concatenated contents of both skins MemoryBuffer<uint8> contents1; @@ -1043,6 +1059,8 @@ TEST_F(SerializerTest, SerializesAllVertexBufferBinaryToSingleFileInArchive) { const AddFileRecord& record = archive_generator_.add_file_records_[0]; EXPECT_EQ("vertex-buffers.bin", record.file_name_); + EXPECT_FALSE(archive_generator_.closed_); + // Test that the data matches what we get if we call SerializeBuffer directly // The file should contain the concatenated contents of both buffers MemoryBuffer<uint8> contents1; diff --git a/o3d/svn_paths.scons b/o3d/svn_paths.scons index 8cb9cde..b78c5c3 100644 --- a/o3d/svn_paths.scons +++ b/o3d/svn_paths.scons @@ -54,6 +54,7 @@ env.Replace( INTERNAL_DIR = '$SCONSTRUCT_DIR/../o3d-internal', JPEG_DIR = '$THIRD_PARTY/jpeg/src', JSDOCTOOLKIT_DIR = '$THIRD_PARTY/jsdoctoolkit/files', + LIBEVENT_DIR = '$THIRD_PARTY/libevent', # To run selenium tests you will need a tgz'ed copy of firefox. # Specify the path to it below. # See tests/build.scons diff --git a/o3d/tests/build.scons b/o3d/tests/build.scons index ee252a2..7b4be1c 100644 --- a/o3d/tests/build.scons +++ b/o3d/tests/build.scons @@ -110,7 +110,7 @@ env.Prepend( 'technique', 'antlr3c', 'gtest', - 'skia' + 'skia', ] + env['ICU_LIBS'], ) @@ -123,6 +123,22 @@ env.Append( LIBS = env['RENDERER_LIBS'], ) +# Add libevent on posix platforms. +if env.Bit('mac') or env.Bit('linux'): + env.Append( + LIBS = [ + 'event', + ], + ) + +# Add cocoa framework on mac. +if env.Bit('mac'): + env.Append( + FRAMEWORKS = [ + 'Cocoa', + ], + ) + run_env = env.Clone() if env.Bit('linux'): run_env.Append(ENV = os.environ) @@ -181,6 +197,7 @@ tests = [ 'core/cross/draw_pass_test.cc', 'core/cross/effect_test.cc', 'core/cross/element_test.cc', + 'core/cross/event_manager_test.cc', 'core/cross/fake_vertex_source.cc', 'core/cross/features_test.cc', 'core/cross/field_test.cc', @@ -223,6 +240,7 @@ tests = [ 'import/cross/gz_compressor_test.cc', 'import/cross/gz_decompressor_test.cc', 'import/cross/json_object_test.cc', + 'import/cross/main_thread_archive_callback_client_test.cc', 'import/cross/memory_buffer_test.cc', 'import/cross/memory_stream_test.cc', 'import/cross/raw_data_test.cc', @@ -230,6 +248,7 @@ tests = [ 'import/cross/tar_processor_test.cc', 'import/cross/targz_generator_test.cc', 'import/cross/targz_processor_test.cc', + 'import/cross/threaded_stream_processor_test.cc', 'serializer/cross/serializer_test.cc', 'tests/common/cross/test_utils.cc', 'utils/cross/file_path_utils_test.cc', @@ -743,7 +762,8 @@ pulse_tests = [env.Alias('presubmit')] # OpenGL tests don't work with selenium yet. if 'RENDERER_D3D9' in env['CPPDEFINES']: pulse_tests += [env.Alias('selenium_firefox'), - env.Alias('selenium_ie')] + env.Alias('selenium_ie'), + env.Alias('selenium_chrome')] # Add selenium for mac. if env.Bit('mac'): pulse_tests += [env.Alias('selenium')] diff --git a/o3d/tests/common/build.scons b/o3d/tests/common/build.scons index 8add24f..69db65f 100644 --- a/o3d/tests/common/build.scons +++ b/o3d/tests/common/build.scons @@ -49,7 +49,7 @@ if env.Bit('linux'): if env.Bit('mac'): inputs += [ - "mac/testing_common.cc", + "mac/testing_common.mm", ] o3dcore_lib = env.ComponentLibrary("testing_common", inputs) diff --git a/o3d/tests/common/cross/main.cc b/o3d/tests/common/cross/main.cc index b4bdbd2..b8c3fc3 100644 --- a/o3d/tests/common/cross/main.cc +++ b/o3d/tests/common/cross/main.cc @@ -32,6 +32,7 @@ // defines the common main() for all unit tests #include <build/build_config.h> +#include "base/at_exit.h" #include "gtest/gtest.h" #ifdef OS_WIN @@ -39,6 +40,7 @@ int test_main(int argc, wchar_t **argv) { #else int test_main(int argc, char **argv) { #endif + ::base::AtExitManager at_exit_manager; testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/o3d/tests/common/mac/testing_common.cc b/o3d/tests/common/mac/testing_common.mm index 2f7cffa..c00ef34 100644 --- a/o3d/tests/common/mac/testing_common.cc +++ b/o3d/tests/common/mac/testing_common.mm @@ -63,6 +63,8 @@ const unsigned int kWindowHeight = 600; extern int test_main(int argc, char **argv); int main(int argc, char *argv[]) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + std::string error; if (!o3d::RendererInstallCheck(&error)) { return false; @@ -132,5 +134,7 @@ int main(int argc, char *argv[]) { g_program_path = NULL; g_program_name = NULL; + [pool release]; + return ret; } diff --git a/o3d/tests/tests.gyp b/o3d/tests/tests.gyp index 80e8da7..d205c11 100644 --- a/o3d/tests/tests.gyp +++ b/o3d/tests/tests.gyp @@ -86,7 +86,7 @@ ['OS == "mac"', { 'sources': [ - 'common/mac/testing_common.cc', + 'common/mac/testing_common.mm', ], 'link_settings': { 'libraries': [ |