The patch below makes your simplified recipe, viz.:
(defvar counter 0)
(defun foo ()
(message (format "foo %s" counter))
(setq counter (1+ counter))
(run-with-idle-timer 1 nil #'foo))
(foo)
"work" without locking up Emacs. "Work" in the sense that the timer
is run and increments the counter, but keyboard input is still
accepted, and causes 1-sec break in the idle timer invocation. What
does NOT happen is the once-per-second invocation of the idle timer:
as long as there's no other input, the idle timer runs much more
frequently. But I think this is expected, since the call to
run-with-idle-timer above explicitly asks to be run immediately.