I am trying to make a program that does the following:
- Processes a list of items.
- Each item is processed by a dedicated function.
- Each function call is wrapped in a with_retries block function.
- Display messages are supposed to be displayed using callbacks.
I have successfully implemented callbacks, error handling, and retry logic, but there is something wrong in my implementation, because combining callbacks with retry logic breaks error handling.
This program:
def print_attempt_number(proc_id:, &callback) random = [true, false].sample if random message = "Attempt for ID: #{proc_id}" callback.call(proc_id, message) if callback # puts "PUTS: #{message}" else raise StandardError, "This is my error message" endenddef handle_error(proc_id:, retries:, max_retries:, &callback) retries += 1 retry_msg = "Retry #{retries} / #{max_retries}" # puts "PUTS: #{retry_msg}" callback.call(proc_id, retry_msg) return retriesenddef with_retries(max_retries:, proc_id:, &callback) retries = 0 begin yield rescue StandardError => e retries = handle_error(proc_id: proc_id, retries: retries, max_retries: max_retries, &callback) sleep 1.0 retry if retries < max_retries endenddef process_item(proc_id:, &callback) init_msg = "Starting with #{proc_id}" callback.call(proc_id, init_msg) with_retries(max_retries: 3, proc_id:) do print_attempt_number(proc_id: proc_id, &callback) endendcallback = Proc.new do |proc_id, message| puts "CALLBACK: #{proc_id}\t#{message}"endfor id in 1..5 do process_item(proc_id: id, &callback) sleep 1.0end
Produces this result:
CALLBACK: 1 Starting with 1CALLBACK: 1 Attempt for ID: 1CALLBACK: 2 Starting with 2CALLBACK: 2 Attempt for ID: 2callback_test.rb:8:in `print_attempt_number': This is my error message (StandardError) from callback_test.rb:38:in `block in process_item' from callback_test.rb:16:in `handle_error' from callback_test.rb:25:in `rescue in with_retries' from callback_test.rb:22:in `with_retries' from callback_test.rb:37:in `process_item' from callback_test.rb:49:in `block in <main>' from callback_test.rb:48:in `each' from callback_test.rb:48:in `<main>'callback_test.rb:8:in `print_attempt_number': This is my error message (StandardError) from callback_test.rb:38:in `block in process_item' from callback_test.rb:23:in `with_retries' from callback_test.rb:37:in `process_item' from callback_test.rb:49:in `block in <main>' from callback_test.rb:48:in `each' from callback_test.rb:48:in `<main>'
However, switching from callback
directly to puts
produces the results as expected:
def print_attempt_number(proc_id:, &callback) random = [true, false].sample if random message = "Attempt for ID: #{proc_id}" # callback.call(proc_id, message) if callback puts "PUTS: #{message}" else raise StandardError, "This is my error message" endenddef handle_error(proc_id:, retries:, max_retries:, &callback) retries += 1 retry_msg = "Retry #{retries} / #{max_retries}" puts "PUTS: #{retry_msg}" # callback.call(proc_id, retry_msg) return retriesenddef with_retries(max_retries:, proc_id:, &callback) retries = 0 begin yield rescue StandardError => e retries = handle_error(proc_id: proc_id, retries: retries, max_retries: max_retries, &callback) sleep 1.0 retry if retries < max_retries endenddef process_item(proc_id:, &callback) init_msg = "Starting with #{proc_id}" callback.call(proc_id, init_msg) with_retries(max_retries: 3, proc_id:) do print_attempt_number(proc_id: proc_id, &callback) endendcallback = Proc.new do |proc_id, message| puts "CALLBACK: #{proc_id}\t#{message}"endfor id in 1..5 do process_item(proc_id: id, &callback) sleep 1.0end
Which produces:
CALLBACK: 1 Starting with 1PUTS: Attempt for ID: 1CALLBACK: 2 Starting with 2PUTS: Retry 1 / 3PUTS: Attempt for ID: 2CALLBACK: 3 Starting with 3PUTS: Retry 1 / 3PUTS: Attempt for ID: 3CALLBACK: 4 Starting with 4PUTS: Retry 1 / 3PUTS: Retry 2 / 3PUTS: Retry 3 / 3CALLBACK: 5 Starting with 5PUTS: Attempt for ID: 5
I don't understand why that happens; how can I achieve the desired printing using callback for print messages?