Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions lib/mixpanel-ruby/flags/local_flags_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ def start_polling_for_definitions!
begin
fetch_flag_definitions
rescue StandardError => e
@error_handler.handle(e) if @error_handler
log_polling_error(e)
end
end
end
end
rescue StandardError => e
@error_handler.handle(e) if @error_handler
log_polling_error(e)
end

def stop_polling_for_definitions!
Expand Down Expand Up @@ -141,6 +141,19 @@ def get_all_variants(context)

private

# Surface polling-loop failures unconditionally. The default
# Mixpanel::ErrorHandler is a no-op, so dispatching only via
# @error_handler swallows schema drift (NoMethodError,
# JSON::ParserError, etc.) — the loop runs forever undetected.
# Always warn so the failure is visible without an error_handler
# being configured. Matches the convention in mixpanel-python /
# mixpanel-java / mixpanel-go / mixpanel-node, all of which log
# unconditionally and keep polling.
def log_polling_error(error)
warn "[Mixpanel] Failed to fetch flag definitions: #{error.class}: #{error.message}"
@error_handler.handle(error) if @error_handler
end

def fetch_flag_definitions
response = call_flags_endpoint

Expand Down
34 changes: 34 additions & 0 deletions spec/mixpanel-ruby/flags/local_flags_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -755,5 +755,39 @@ def user_context_with_properties(properties)
polling_provider.stop_polling_for_definitions!
end
end

it 'warns to stderr when fetch raises and no error_handler is configured' do
stub_request(:get, endpoint_url_regex).to_return(status: 500, body: 'server down')

polling_provider = Mixpanel::Flags::LocalFlagsProvider.new(
test_token,
{ enable_polling: false },
mock_tracker,
nil
)

expect { polling_provider.start_polling_for_definitions! }
.to output(/\[Mixpanel\] Failed to fetch flag definitions: Mixpanel::ServerError/).to_stderr
end

it 'surfaces unexpected errors (schema drift) instead of swallowing them silently' do
stub_flag_definitions([create_test_flag])

polling_provider = Mixpanel::Flags::LocalFlagsProvider.new(
test_token,
{ enable_polling: false },
mock_tracker,
mock_error_handler
)

# Simulate schema drift: server returns a payload the parser doesn't expect.
# Without this fix the error would be dispatched only to @error_handler (whose
# default ErrorHandler#handle is a no-op) — operators would never notice.
allow(polling_provider).to receive(:fetch_flag_definitions).and_raise(NoMethodError, "undefined method `[]' for nil:NilClass")

expect(mock_error_handler).to receive(:handle).with(an_instance_of(NoMethodError))
expect { polling_provider.start_polling_for_definitions! }
.to output(/\[Mixpanel\] Failed to fetch flag definitions: NoMethodError/).to_stderr
end
end
end
Loading