Index: design/admin/templates/setup/session.tpl
===================================================================
--- design/admin/templates/setup/session.tpl	(revision 24326)
+++ design/admin/templates/setup/session.tpl	(working copy)
@@ -6,9 +6,19 @@
 
 {* Messages *}
 {if $sessions_removed}
-<div class="message-feedback">
-<h2><span class="time">[{currentdate()|l10n( shortdatetime )}]</span> {"The sessions were successfully removed."|i18n( "design/admin/setup/session" )}</h2>
-</div>
+    {if $gc_sessions_completed}
+		<div class="message-feedback">
+		<h2><span class="time">[{currentdate()|l10n( shortdatetime )}]</span> {"The sessions were successfully removed."|i18n( "design/admin/setup/session" )}</h2>
+		</div>
+    {else}
+		<div class="message-feedback">
+		    <h2><span class="time">[{currentdate()|l10n( shortdatetime )}]</span> {"Some timed out sessions were successfully removed."|i18n( "design/admin/setup/session" )}</h2>
+		    <p>
+		        {"But to avoid execution timeout you'll need to perform the action several times to complete the operation."|i18n( "design/admin/setup/session" )}<br />
+		        {"Alternatively clear the timed out session data from command-line using:"|i18n( "design/admin/setup/session" )} <i>&gt;php bin/php/ezsessiongc.php</i>
+		    </p>
+		</div>
+    {/if}
 {/if}
 
 
Index: design/standard/templates/setup/session.tpl
===================================================================
--- design/standard/templates/setup/session.tpl	(revision 24326)
+++ design/standard/templates/setup/session.tpl	(working copy)
@@ -4,36 +4,32 @@
 <!--
 function checkAll()
 {
-
     if ( document.trashaction.selectall.value == "Select all" )
-
     {
-
         document.trashaction.selectall.value = "Deselect all";
 
         with (document.trashaction)
-	{
+        {
             for (var i=0; i < elements.length; i++)
-	    {
+            {
                 if (elements[i].type == 'checkbox' && ( elements[i].name == 'SessionKeyArray[]' || elements[i].name == 'UserIDArray[]' ) )
                      elements[i].checked = true;
-	    }
+            }
         }
-     }
-     else
-     {
-
-         document.trashaction.selectall.value = "Select all";
+    }
+    else
+    {
+        document.trashaction.selectall.value = "Select all";
 
-         with (document.trashaction)
-	 {
+        with (document.trashaction)
+        {
             for (var i=0; i < elements.length; i++)
-	    {
+            {
                 if (elements[i].type == 'checkbox' && ( elements[i].name == 'SessionKeyArray[]' || elements[i].name == 'UserIDArray[]' ) )
                      elements[i].checked = false;
-	    }
-         }
-     }
+            }
+        }
+    }
 }
 
 //-->
@@ -43,9 +39,19 @@
 <h1>{"Session admin"|i18n( "design/standard/setup/session" )}</h1>
 
 {if $sessions_removed}
-<div class="feedback">
-{"The sessions were successfully removed."|i18n( "design/standard/setup/session" )}
-</div>
+    {if $gc_sessions_completed}
+	  <div class="feedback">
+	    {"The sessions were successfully removed."|i18n( "design/standard/setup/session" )}
+	  </div>
+	{else}
+      <div class="feedback">
+            <h2>{"Some timed out sessions were successfully removed."|i18n( "design/admin/setup/session" )}</h2>
+            <p>
+                {"But to avoid execution timeout you'll need to perform the action several times to complete the operation."|i18n( "design/admin/setup/session" )}<br />
+                {"Alternatively clear the timed out session data from command-line using:"|i18n( "design/admin/setup/session" )} <i>&gt;php bin/php/ezsessiongc.php</i>
+            </p>
+      </div>
+    {/if}
 {/if}
 
 <div class="objectheader">
Index: kernel/setup/session.php
===================================================================
--- kernel/setup/session.php	(revision 24326)
+++ kernel/setup/session.php	(working copy)
@@ -29,6 +29,7 @@
 
 $tpl = templateInit();
 $sessionsRemoved = false;
+$gcSessionsCompleted = true;
 $http = eZHTTPTool::instance();
 
 $module = $Params['Module'];
@@ -74,7 +75,7 @@
 }
 else if ( $module->isCurrentAction( 'RemoveTimedOutSessions' ) )
 {
-    eZSession::garbageCollector();
+    $gcSessionsCompleted = eZSession::garbageCollector();
     $sessionsRemoved = true;
 }
 else if ( $module->isCurrentAction( 'RemoveSelectedSessions' ) )
@@ -352,6 +353,7 @@
     $module->redirectTo( '/setup/session' );
 }
 
+$tpl->setVariable( "gc_sessions_completed", $gcSessionsCompleted );
 $tpl->setVariable( "sessions_removed", $sessionsRemoved );
 $tpl->setVariable( "sessions_active", $sessionsActive );
 $tpl->setVariable( "sessions_count", $sessionsCount );
Index: lib/ezutils/classes/ezsession.php
===================================================================
--- lib/ezutils/classes/ezsession.php	(revision 24326)
+++ lib/ezutils/classes/ezsession.php	(working copy)
@@ -330,18 +330,48 @@
      * Deletes all expired session data in the database, this function is 
      * register in {@link eZSession::registerFunctions()}
      * 
-     * @static
+     * @param int $commitrows When > 0, a db commit will be issue after every
+     *            chunk of commitrows has been deleted avoiding long table locks and and timeouts
+     *            if number is sufficiently low. 
+     * @return bool Return boolean to signal if gc completed or if it stopped to avoid timeout.
      */
-    static public function garbageCollector()
+    static public function garbageCollector( $commitrows = 10000 )
     {
         $db = eZDB::instance();
-        $time = time();
+        $gcCompleted = true;
+        $requestTime = $_SERVER['REQUEST_TIME'];
+        // Get max execution time, set to 300 if unlimited to avoid triggering other timouts
+        $maxExecutionTime = ini_get( 'max_execution_time' ) || 300;
+        self::triggerCallback( 'gc_pre', array( $db, $requestTime ) );
 
-        self::triggerCallback( 'gc_pre', array( $db, $time ) );
-
-        $db->query( "DELETE FROM ezsession WHERE expiration_time < $time" );
+        if ( $commitrows !== 0 )
+        {
+            // Calculate approx. the slicing of timestamps that will delete N rows at a time
+            // assuming that sessions are evenly distributed in time
+            $res = $db->arrayQuery( 'SELECT COUNT(*) AS count, MIN(expiration_time) AS oldest FROM ezsession WHERE expiration_time < ' . $requestTime );
+            if ( $res && $res[0]['count'] > 0 && $res[0]['oldest'] < $requestTime )
+            {
+                $date      = $res[0]['oldest'];
+                $timeslice = intval( $commitrows * ( $requestTime - $res[0]['oldest'] ) / $res[0]['count'] );
+                do
+                {
+                    $date += $timeslice;
+                    if ( $date > $requestTime ) $date = $requestTime;
+                    $db->query( 'DELETE FROM ezsession WHERE expiration_time < ' . $date );
+                    $db->commitquery();
+                    // Make sure we're in a safe distance (above 5 seconds) from execution timeout
+                    $withinTimout = ( time() - $requestTime  ) < ( $maxExecutionTime - 5 );
+                } while ( $date < $requestTime && $withinTimout );
+                $gcCompleted = $withinTimout || $date >= $requestTime;
+            }
+        }
+        else
+        {
+            $db->query( 'DELETE FROM ezsession WHERE expiration_time < ' . $requestTime );
+        }
 
-        self::triggerCallback( 'gc_post', array( $db, $time ) );
+        self::triggerCallback( 'gc_post', array( $db, $requestTime ) );
+        return $gcCompleted;
     }
 
     /**
