Commit 52a2a9b0 authored by Phillip Susi's avatar Phillip Susi Committed by Curtis Gedak

Reduce threading (#685740)

Win_Gparted and Dialog_Progress were creating threads to perform most
functions in the background.  Most of the time, the only reason the
threads blocked was to execute an external command.  The external command
execution has been changed to spawn the command asynchronously and wait
for completion with a nested main loop.  While waiting for completion,
the pipe output is captured via events.  In the future, this will allow
for it to be parsed in real time to obtain progress information.

Those tasks in GParted_Core that still block now spawn a background thread
and wait for it to complete with a nested main loop to avoid hanging the
gui.

Part of Bug #685740 - Refactor to use asynchronous command execution
parent 2706f017
......@@ -46,11 +46,10 @@ public:
private:
void on_signal_update( const OperationDetail & operationdetail ) ;
void dispatcher_on_update_gui_elements() ;
void update_gui_elements() ;
void on_signal_show() ;
void on_expander_changed() ;
void on_cell_data_description( Gtk::CellRenderer * renderer, const Gtk::TreeModel::iterator & iter) ;
void thread_apply_operation();
void on_cancel() ;
void on_save() ;
void echo_operation_details( const OperationDetail & operation_detail, std::ofstream & out ) ;
......@@ -97,7 +96,6 @@ private:
double fraction ;
unsigned int t, warnings ;
sigc::connection pulsetimer;
Glib::Dispatcher dispatcher_update_gui_elements ;
Glib::ustring label_current_sub_text ;
};
......
......@@ -21,6 +21,7 @@
#define DEFINE_FILESYSTEM
#include "../include/Operation.h"
#include "../include/PipeCapture.h"
#include <fstream>
#include <sys/stat.h>
......@@ -56,12 +57,12 @@ public:
OperationDetail & operationdetail ) = 0 ;
virtual bool check_repair( const Partition & partition, OperationDetail & operationdetail ) = 0 ;
virtual bool remove( const Partition & partition, OperationDetail & operationdetail ) = 0 ;
bool success;
protected:
int execute_command( const Glib::ustring & command, OperationDetail & operationdetail ) ;
int execute_command_timed( const Glib::ustring & command
, OperationDetail & operationdetail
, bool check_status = true ) ;
int execute_command( const Glib::ustring & command, OperationDetail & operationdetail, bool checkstatus = false );
int execute_command_timed( const Glib::ustring & command, OperationDetail & operationdetail ) {
return execute_command( command, operationdetail, true ); }
void execute_command_eof();
Glib::ustring mk_temp_dir( const Glib::ustring & infix, OperationDetail & operationdetail ) ;
void rm_temp_dir( const Glib::ustring dir_name, OperationDetail & operationdetail ) ;
......@@ -72,7 +73,9 @@ protected:
unsigned int index ;
private:
void store_exit_status( GPid pid, int status );
bool running;
int pipecount;
};
} //GParted
......
......@@ -42,6 +42,7 @@ public:
void find_supported_filesystems() ;
void set_user_devices( const std::vector<Glib::ustring> & user_devices ) ;
void set_devices( std::vector<Device> & devices ) ;
void set_devices_thread( std::vector<Device> * pdevices );
void guess_partition_table(const Device & device, Glib::ustring &buff);
bool snap_to_cylinder( const Device & device, Partition & partition, Glib::ustring & error ) ;
......
......@@ -55,4 +55,5 @@ EXTRA_DIST = \
reiser4.h \
reiserfs.h \
ufs.h \
xfs.h
xfs.h \
PipeCapture.h
#ifndef PIPECAPTURE_H
#define PIPECAPTURE_H
#include <glibmm/ustring.h>
#include <glibmm/main.h>
#include <glibmm/iochannel.h>
namespace GParted {
// captures output pipe of subprocess into a ustring and emits a signal on eof
class PipeCapture
{
Glib::ustring &buff;
unsigned int backcount;
unsigned int linelength;
Glib::RefPtr<Glib::IOChannel> channel;
sigc::connection connection;
bool OnReadable( Glib::IOCondition condition );
public:
PipeCapture( int fd, Glib::ustring &buffer );
void connect_signal( int fd );
~PipeCapture();
sigc::signal<void> eof;
sigc::signal<void> update;
};
} // namepace GParted
#endif
......@@ -178,7 +178,6 @@ public:
const char drive_letter, const Glib::ustring & device_path ) ;
static Glib::ustring delete_mtoolsrc_file( const char file_name[] ) ;
static Glib::ustring trim( const Glib::ustring & src, const Glib::ustring & c = " \t\r\n" ) ;
static Glib::ustring cleanup_cursor( const Glib::ustring & text ) ;
static Glib::ustring get_lang() ;
static void tokenize( const Glib::ustring& str,
std::vector<Glib::ustring>& tokens,
......@@ -196,7 +195,6 @@ private:
static bool get_kernel_version( int & major_ver, int & minor_ver, int & patch_ver ) ;
};
}//GParted
#endif //UTILS
......@@ -55,7 +55,7 @@ private:
void refresh_combo_devices() ;
void show_pulsebar( const Glib::ustring & status_message ) ;
void hide_pulsebar();
//Fill txtview_device_info_buffer with some information about the selected device
void Fill_Label_Device_Info( bool clear = false );
......@@ -126,12 +126,7 @@ private:
}
//threads..
void thread_refresh_devices() ;
void thread_unmount_partition( bool * succes, Glib::ustring * error ) ;
void thread_mount_partition( Glib::ustring mountpoint, bool * succes, Glib::ustring * error ) ;
void thread_toggle_swap( bool * succes, Glib::ustring * error ) ;
void thread_toggle_lvm2_pv( bool * succes, Glib::ustring * error ) ;
void thread_guess_partition_table();
void unmount_partition( bool * succes, Glib::ustring * error );
//signal handlers
void open_operationslist() ;
......@@ -260,6 +255,7 @@ private:
//stuff for progress overview and pulsebar
bool pulsebar_pulse();
sigc::connection pulsetimer;
};
} //GParted
......
......@@ -38,9 +38,6 @@ Dialog_Progress::Dialog_Progress( const std::vector<Operation *> & operations )
fraction = 1.00 / operations .size() ;
dispatcher_update_gui_elements .connect(
sigc::mem_fun( this, &Dialog_Progress::dispatcher_on_update_gui_elements ) ) ;
{
Gtk::VBox* vbox(manage(new Gtk::VBox()));
......@@ -158,12 +155,12 @@ void Dialog_Progress::on_signal_update( const OperationDetail & operationdetail
if ( operationdetail .get_status() == STATUS_EXECUTE )
label_current_sub_text = operationdetail .get_description() ;
dispatcher_update_gui_elements() ;
if ( operationdetail.fraction >= 0 ) {
pulsetimer.disconnect();
progressbar_current.set_fraction( operationdetail.fraction > 1.0 ? 1.0 : operationdetail.fraction );
} else if( !pulsetimer.connected() )
pulsetimer = Glib::signal_timeout().connect( sigc::mem_fun(*this, &Dialog_Progress::pulsebar_pulse), 100 );
update_gui_elements();
}
else//it's an new od which needs to be added to the model.
{
......@@ -182,7 +179,7 @@ void Dialog_Progress::on_signal_update( const OperationDetail & operationdetail
}
}
void Dialog_Progress::dispatcher_on_update_gui_elements()
void Dialog_Progress::update_gui_elements()
{
label_current_sub .set_markup( "<i>" + label_current_sub_text + "</i>\n" ) ;
......@@ -216,11 +213,7 @@ void Dialog_Progress::on_signal_show()
//set focus...
treeview_operations .set_cursor( static_cast<Gtk::TreePath>( treerow ) ) ;
//and start..
Glib::Thread::create( sigc::mem_fun(
*this, &Dialog_Progress::thread_apply_operation ),
false );
Gtk::Main::run();
succes = signal_apply_operation.emit( operations[t] );
//set status (succes/error) for this operation
operations[ t ] ->operation_detail .set_status( succes ? STATUS_SUCCES : STATUS_ERROR ) ;
......@@ -299,18 +292,6 @@ void Dialog_Progress::on_cell_data_description( Gtk::CellRenderer * renderer, co
static_cast<Gtk::TreeRow>( *iter )[ treeview_operations_columns .operation_description ] ;
}
static bool _mainquit( void *dummy )
{
Gtk::Main::quit();
return false;
}
void Dialog_Progress::thread_apply_operation()
{
succes = signal_apply_operation.emit( operations[t] );
g_idle_add( (GSourceFunc)_mainquit, NULL );
}
void Dialog_Progress::on_cancel()
{
Gtk::MessageDialog dialog( *this,
......
......@@ -17,12 +17,16 @@
#include "../include/FileSystem.h"
#include "../include/GParted_Core.h"
#include <cerrno>
#include <iostream>
#include <gtkmm/main.h>
#include <fcntl.h>
namespace GParted
{
FileSystem::FileSystem()
{
}
......@@ -48,44 +52,87 @@ const Glib::ustring FileSystem::get_generic_text( CUSTOM_TEXT ttype, int index )
}
}
int FileSystem::execute_command( const Glib::ustring & command, OperationDetail & operationdetail )
void FileSystem::store_exit_status( GPid pid, int status )
{
operationdetail .add_child( OperationDetail( command, STATUS_NONE, FONT_BOLD_ITALIC ) ) ;
int exit_status = Utils::execute_command( "nice -n 19 " + command, output, error ) ;
if ( ! output .empty() )
operationdetail .get_last_child() .add_child( OperationDetail( output, STATUS_NONE, FONT_ITALIC ) ) ;
if ( ! error .empty() )
operationdetail .get_last_child() .add_child( OperationDetail( error, STATUS_NONE, FONT_ITALIC ) ) ;
return exit_status ;
exit_status = status;
running = false;
if (pipecount == 0) // pipes finished first
Gtk::Main::quit();
Glib::spawn_close_pid( pid );
}
//Time command, add results to operation detail and by default set success or failure
int FileSystem::execute_command_timed( const Glib::ustring & command
, OperationDetail & operationdetail
, bool check_status )
static void relay_update( OperationDetail *operationdetail, Glib::ustring *str )
{
operationdetail .add_child( OperationDetail( command, STATUS_EXECUTE, FONT_BOLD_ITALIC ) ) ;
operationdetail->set_description( *str, FONT_ITALIC );
}
int exit_status = Utils::execute_command( "nice -n 19 " + command, output, error ) ;
if ( check_status )
{
if ( ! exit_status )
operationdetail .get_last_child() .set_status( STATUS_SUCCES ) ;
int FileSystem::execute_command( const Glib::ustring & command, OperationDetail & operationdetail, bool checkstatus )
{
operationdetail .add_child( OperationDetail( command, checkstatus ? STATUS_EXECUTE : STATUS_NONE, FONT_BOLD_ITALIC ) ) ;
Glib::Pid pid;
// set up pipes for capture
int out, err;
// spawn external process
running = true;
try {
Glib::spawn_async_with_pipes(
std::string(),
Glib::shell_parse_argv( command ),
Glib::SPAWN_DO_NOT_REAP_CHILD | Glib::SPAWN_SEARCH_PATH,
sigc::slot< void >(),
&pid,
0,
&out,
&err );
} catch (Glib::SpawnError &e) {
std::cerr << e.what() << std::endl;
operationdetail.get_last_child().add_child(
OperationDetail( e.what(), STATUS_ERROR, FONT_ITALIC ) );
return 1;
}
fcntl( out, F_SETFL, O_NONBLOCK );
fcntl( err, F_SETFL, O_NONBLOCK );
Glib::signal_child_watch().connect( sigc::mem_fun( *this, &FileSystem::store_exit_status ), pid );
output.clear();
error.clear();
pipecount = 2;
PipeCapture outputcapture( out, output );
PipeCapture errorcapture( err, error );
outputcapture.eof.connect( sigc::mem_fun( *this, &FileSystem::execute_command_eof ) );
errorcapture.eof.connect( sigc::mem_fun( *this, &FileSystem::execute_command_eof ) );
operationdetail.get_last_child().add_child(
OperationDetail( output, STATUS_NONE, FONT_ITALIC ) );
operationdetail.get_last_child().add_child(
OperationDetail( error, STATUS_NONE, FONT_ITALIC ) );
std::vector<OperationDetail> &children = operationdetail.get_last_child().get_childs();
outputcapture.update.connect( sigc::bind( sigc::ptr_fun( relay_update ),
&(children[children.size() - 2]),
&output ) );
errorcapture.update.connect( sigc::bind( sigc::ptr_fun( relay_update ),
&(children[children.size() - 1]),
&error ) );
outputcapture.connect_signal( out );
errorcapture.connect_signal( err );
Gtk::Main::run();
if (checkstatus) {
if ( !exit_status )
operationdetail.get_last_child().set_status( STATUS_SUCCES );
else
operationdetail .get_last_child() .set_status( STATUS_ERROR ) ;
operationdetail.get_last_child().set_status( STATUS_ERROR );
}
close( out );
close( err );
return exit_status;
}
if ( ! output .empty() )
operationdetail .get_last_child() .add_child( OperationDetail( output, STATUS_NONE, FONT_ITALIC ) ) ;
if ( ! error .empty() )
operationdetail .get_last_child() .add_child( OperationDetail( error, STATUS_NONE, FONT_ITALIC ) ) ;
return exit_status ;
void FileSystem::execute_command_eof()
{
if (--pipecount)
return; // wait for second pipe to eof
if ( !running ) // already got exit status
Gtk::Main::quit();
}
//Create uniquely named temporary directory and add results to operation detail
......
......@@ -57,6 +57,7 @@
#include <dirent.h>
#include <mntent.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/main.h>
std::vector<Glib::ustring> libparted_messages ; //see ped_exception_handler()
......@@ -138,6 +139,22 @@ void GParted_Core::set_user_devices( const std::vector<Glib::ustring> & user_dev
void GParted_Core::set_devices( std::vector<Device> & devices )
{
Glib::Thread::create( sigc::bind(
sigc::mem_fun( *this, &GParted_Core::set_devices_thread ),
&devices),
false );
Gtk::Main::run();
}
static bool _mainquit( void *dummy )
{
Gtk::Main::quit();
return false;
}
void GParted_Core::set_devices_thread( std::vector<Device> * pdevices )
{
std::vector<Device> &devices = *pdevices;
devices .clear() ;
Device temp_device ;
Proc_Partitions_Info pp_info( true ) ; //Refresh cache of proc partition information
......@@ -325,6 +342,7 @@ void GParted_Core::set_devices( std::vector<Device> & devices )
//NOTE that we cannot clear mountinfo since it might be needed in get_all_mountpoints()
set_thread_status_message("") ;
fstab_info .clear() ;
g_idle_add( (GSourceFunc)_mainquit, NULL );
}
// runs gpart on the specified parameter
......@@ -3548,15 +3566,15 @@ public:
struct ped_exception_ctx {
PedExceptionOption ret;
PedException *e;
Glib::Threads::Mutex mutex;
Glib::Threads::Cond cond;
Glib::Mutex mutex;
Glib::Cond cond;
};
static bool _ped_exception_handler( struct ped_exception_ctx *ctx )
{
std::cout << ctx->e->message << std::endl;
std::cerr << ctx->e->message << std::endl;
libparted_messages.push_back( ctx->e->message );
libparted_messages.push_back( ctx->e->message );
char optcount = 0;
int opt = 0;
for( char c = 0; c < 10; c++ )
......
......@@ -65,7 +65,8 @@ gpartedbin_SOURCES = \
reiser4.cc \
reiserfs.cc \
ufs.cc \
xfs.cc
xfs.cc \
PipeCapture.cc
gpartedbin_LDFLAGS = -lparted
......
......@@ -146,7 +146,7 @@ OperationDetail & OperationDetail::get_last_child()
if ( sub_details .size() == 0 )
add_child( OperationDetail( "---", STATUS_ERROR ) ) ;
return sub_details .back() ;
return sub_details[sub_details.size() - 1];
}
void OperationDetail::on_update( const OperationDetail & operationdetail )
......
#include "../include/PipeCapture.h"
#include <iostream>
namespace GParted {
PipeCapture::PipeCapture( int fd, Glib::ustring &string ) : buff( string ), backcount( 0 ), linelength( 0 )
{
// tie fd to string
// make channel
channel = Glib::IOChannel::create_from_fd( fd );
}
void PipeCapture::connect_signal( int fd )
{
// connect handler to signal input/output
connection = Glib::signal_io().connect(
sigc::mem_fun( *this, &PipeCapture::OnReadable ),
fd,
Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR );
}
bool PipeCapture::OnReadable( Glib::IOCondition condition )
{
// read from pipe and store in buff
Glib::ustring str;
Glib::IOStatus status = channel->read( str, 512 );
if (status == Glib::IO_STATUS_NORMAL)
{
for( Glib::ustring::iterator s = str.begin();
s != str.end(); s++ )
{
if( *s == '\b' )
backcount++;
else if( *s == '\r' )
backcount = linelength;
else if( *s == '\n' ) {
linelength = 0;
buff += '\n';
backcount = 0;
}
else {
if (backcount) {
buff.erase( buff.length() - backcount, backcount );
linelength -= backcount;
backcount = 0;
}
buff += *s;
++linelength;
}
}
update();
return true;
}
if (status != Glib::IO_STATUS_EOF)
std::cerr << "Pipe IOChannel read failed" << std::endl;
// signal completion
connection.disconnect();
eof();
return false;
}
PipeCapture::~PipeCapture()
{
connection.disconnect();
}
} // namespace GParted
......@@ -17,6 +17,8 @@
*/
#include "../include/Utils.h"
#include "../include/GParted_Core.h"
#include "../include/PipeCapture.h"
#include <sstream>
#include <fstream>
......@@ -26,7 +28,8 @@
#include <uuid/uuid.h>
#include <cerrno>
#include <sys/statvfs.h>
#include <gtkmm/main.h>
#include <fcntl.h>
namespace GParted
{
......@@ -269,7 +272,7 @@ bool Utils::kernel_supports_fs( const Glib::ustring & fs )
return true ;
Glib::ustring output, error ;
execute_command( "modprobe " + fs, output, error, true ) ;
execute_command( "modprobe " + fs, output, error, true );
input .open( "/proc/filesystems" ) ;
if ( input )
......@@ -380,62 +383,113 @@ int Utils::execute_command( const Glib::ustring & command )
return execute_command( command, dummy, dummy ) ;
}
int Utils::execute_command( const Glib::ustring & command,
Glib::ustring & output,
Glib::ustring & error,
bool use_C_locale )
class utils_execute_command_status
{
int exit_status = -1 ;
std::string std_out, std_error ;
try
public:
bool running;
int pipecount;
int exit_status;
bool foreground;
Glib::Mutex mutex;
Glib::Cond cond;
void store_exit_status( GPid pid, int status );
void execute_command_eof();
};
void utils_execute_command_status::store_exit_status( GPid pid, int status )
{
exit_status = status;
running = false;
if (pipecount == 0) // pipes finished first
{
std::vector<std::string>argv;
argv .push_back( "sh" ) ;
argv .push_back( "-c" ) ;
argv .push_back( command ) ;
if ( use_C_locale )
{
//Spawn command using the C language environment
std::vector<std::string> envp ;
envp .push_back( "LC_ALL=C" ) ;
envp .push_back( "PATH=" + Glib::getenv( "PATH" ) ) ;
Glib::spawn_sync( "."
, argv
, envp
, Glib::SPAWN_SEARCH_PATH
, sigc::slot<void>()
, &std_out
, &std_error
, &exit_status
) ;
}
else
{
//Spawn command inheriting the parent's environment
Glib::spawn_sync( "."
, argv
, Glib::SPAWN_SEARCH_PATH
, sigc::slot<void>()
, &std_out
, &std_error
, &exit_status
) ;
if (foreground)
Gtk::Main::quit();
else {
mutex.lock();
cond.signal();
mutex.unlock();
}
}
catch ( Glib::Exception & e )
{
error = e .what() ;
Glib::spawn_close_pid( pid );
}
return -1 ;
void utils_execute_command_status::execute_command_eof()
{
if (--pipecount)
return; // wait for second pipe to eof
if ( !running ) // already got exit status
{
if (foreground)
Gtk::Main::quit();
else {
mutex.lock();
cond.signal();
mutex.unlock();
}
}
}
output = Utils::cleanup_cursor( std_out ) ;
error = std_error ;
static void set_locale()
{
setenv( "LC_ALL", "C", 1 );
}
return exit_status ;
int Utils::execute_command( const Glib::ustring & command,
Glib::ustring & output,
Glib::ustring & error,
bool use_C_locale )
{
Glib::Pid pid;
// set up pipes for capture
int out, err;
utils_execute_command_status status;
// spawn external process
status.running = true;
status.pipecount = 2;
status.foreground = (Glib::Thread::self() == GParted_Core::mainthread);
try {
Glib::spawn_async_with_pipes(
std::string(),
Glib::shell_parse_argv( command ),
Glib::SPAWN_DO_NOT_REAP_CHILD | Glib::SPAWN_SEARCH_PATH,