How does the batch API work internally?
This is how batch works (Based on my Understanding)

1. Initialize


Initialize the batch processing. Based on the clients(Browsers)  configuration on whether JavaScript is enabled or not.
JavaScript-enabled clients are identified by the 'has_js' cookie set in   drupal.js. If no JavaScript-enabled page has been visited during the current  user's browser session, the non-JavaScript version is returned.
If JavaScript enabled Batch uses ajax request the keep the connection alive across the request.
If JavaScript is not enabled Batch uses sets a meta tag in html to make regular refresh  intervals to the keep the connection alive across the request.


(This is how the progress bar is updated on the progress of the Job Done.)

Batch Process


For starting the process, Batch creates a Queue and adds all the operations(functions and arguments) that you define in the batch array like,

$batch = array (
'operations' =&gt; array(
  array('batch_example_process', array($options1, $options2)),
  array('batch_example_process', array($options3, $options4)),
  ),
'finished' =&gt; 'batch_example_finished',
'title' =&gt; t('Processing Example Batch'),
'init_message' =&gt; t('Example Batch is starting.'),
'progress_message' =&gt; t('Processed @current out of @total.'),
'error_message' =&gt; t('Example Batch has encountered an error.'),
'file' =&gt; drupal_get_path('module', 'batch_example') . '/batch_example.inc',
);


Additionally it also assigns  a batch id which is unique across the batches.
Now Batch calls claims the Queue items one by one and executes the function defined with the arguments that are defined in it.
This is a crucial part, The function(Operation) that implements the batch operation should chunk the data and process the data very efficiently keeping in mind of PHP's Memory limit, Time out. Failing to do so will end up in your problem.



  I ran into a timeout issue using migrate the other day and started to wonder how the batch API works internally.


The Batch Function

The functions that implement Batch should take of the following things very carefully,


Number of Items within the operations to process like,

if (!isset($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_node'] = 0;
$context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
}

Limiting the number of items to process in one function call like setting up a limit,

// For this example, we decide that we can safely process 5 nodes at a time without a timeout.
$limit = 5;

Update on the process to post-processing like,

// Update our progress information.
    $context['sandbox']['progress']++;
    $context['sandbox']['current_node'] = $node-&gt;nid;
    $context['message'] = t('Now processing %node', array('%node' =&gt; $node-&gt;title));

Informing the Batch engine whether the Batch is completed or Not like,

// Inform the batch engine that we are not finished,
// and provide an estimation of the completion level we reached.
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
  $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
 }



Most of the Above Points are taken care of the Drupal's Core batch operations if it is Missed in the Implementing function. But it is always best to define in the implementing function

Batch Finished callback


This is the last call back called when defined in the batch array Usually a report of how much processed etc...


ANSWERS


  If the page with the batch request is closed does the batch processing
  stop? Will it restart when the same url is opened again? The migrate
  module sometimes continues but it's probably using queues?


Yes, Ideally it should restart the batch and as said above it is based on the function you implement.

To solve your problem of PHP Time out use Drush batch which is available in migrate module, But first dig out migrate's batch functions and try to chunk your processing data.