Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Concurrent routines for Ruby.
Goru is an experimental concurrency library for Ruby.
Goru was intended for low-level programs like http servers and not for direct use in user-facing code.
Routines are defined with initial state and a block that does work and (optionally) updates the state of the routine:
3.times do
Goru::Scheduler.go(:running) { |routine|
case routine.state
when :running
routine.update(:sleeping)
routine.sleep(rand)
when :sleeping
puts "[#{object_id}] woke up at #{Time.now.to_f}"
routine.update(:running)
end
}
end
Routines run concurrently within a reactor, each reactor running in a dedicated thread. Each eligible routine is called once on every tick of the reactor it is scheduled to run in. In the example above, the three routines sleep for a random interval before waking up and printing the current time. Here is some example output:
[1840] woke up at 1677939216.379147
[1860] woke up at 1677939217.059535
[1920] woke up at 1677939217.190349
[1860] woke up at 1677939217.6196458
[1920] woke up at 1677939217.935916
[1840] woke up at 1677939218.033243
[1860] woke up at 1677939218.532908
[1920] woke up at 1677939218.8669178
[1840] woke up at 1677939219.379714
[1860] woke up at 1677939219.522777
[1920] woke up at 1677939220.0475688
[1840] woke up at 1677939220.253979
Each reactor can only run one routine at any given point in time, but if a routine blocks (e.g. by sleeping or performing i/o) the reactor calls another eligible routine before returning to the previously blocked routine on the next tick.
By default Goru routines are scheduled in a global scheduler that waits at the end of the program for all routines to finish. While this is useful for small scripts, most use-cases will involve creating your own scheduler and registering routines directly:
scheduler = Goru::Scheduler.new
scheduler.go { |routine|
...
}
scheduler.wait
Routines are scheduled to run immediately after registration.
Schedulers default to running a number of reactors matching the number of processors on the current system. Tune
this to your needs with the count
option when creating a scheduler:
scheduler = Goru::Scheduler.new(count: 3)
Routines are initialized with default state that is useful for coordination between ticks. This is perhaps the oddest part of Goru but the explicitness can make it easier to understand exactly how your routines will behave.
Take a look at the examples to get some ideas.
Routines will run forever until you say they are finished:
Goru::Scheduler.go { |routine|
routine.finished
}
When finishing a routine you can provide a final result:
routines = []
scheduler = Goru::Scheduler.new
routines << scheduler.go { |routine| routine.finished(true) }
routines << scheduler.go { |routine| routine.finished(false) }
routines << scheduler.go { |routine| routine.finished(true) }
scheduler.wait
pp routines.map(&:result)
# [true, false, true]
Unhandled errors within a routine cause the routine to enter an :errored
state. Calling result
on an errored
routine causes the error to be re-raised. Routines can handle errors elegantly using the handle
method:
Goru::Scheduler.go { |routine|
routine.handle(StandardError) do |event:|
# do something with `event`
end
...
}
Goru implements a non-blocking version of sleep
that makes the routine ineligible to be called until the sleep time
has elapsed. It is important to note that Ruby's built-in sleep method will block the reactor and should not be used.
Goru::Scheduler.go { |routine|
routine.sleep(3)
}
Unlike Kernel#sleep
Goru's sleep method requires a duration.
Goru offers buffered reading and writing through channels:
channel = Goru::Channel.new
Goru::Scheduler.go(channel: channel, intent: :w) { |routine|
routine << SecureRandom.hex
}
# This routine is not invoked unless the channel contains data for reading.
#
Goru::Scheduler.go(channel: channel, intent: :r) { |routine|
value = routine.read
}
Channels are unbounded by default, meaning they can hold an unlimited amount of data. This behavior can be changed by initializing a channel with a specific size. Routines with the intent to write will not be invoked unless the channel has space available for writing.
channel = Goru::Channel.new(size: 3)
# This routine is not invoked if the channel is full.
#
Goru::Scheduler.go(channel: channel, intent: :w) { |routine|
routine << SecureRandom.hex
}
Goru includes a pattern for non-blocking io. With it you can implement non-blocking servers, clients, etc.
Routines that involve io must be created with an io object and an intent. Possible intents include:
:r
for reading:w
for writing:rw
for reading and writingHere is the beginning of an http server in Goru:
Goru::Scheduler.go(io: TCPServer.new("localhost", 4242), intent: :r) { |server_routine|
next unless client = server_routine.accept
Goru::Scheduler.go(io: client, intent: :r) { |client_routine|
next unless data = client_routine.read(16384)
# do something with `data`
}
}
Intents can be changed after a routine is created, e.g. to switch a routine from reading to writing:
Goru::Scheduler.go(io: io, intent: :r) { |routine|
routine.intent = :w
}
Goru supports coordinated buffered io using bridges:
writer = Goru::Channel.new
Goru::Scheduler.go(io: io, intent: :r) { |routine|
case routine.intent
when :r
routine.bridge(intent: :w, channel: writer) { |bridge|
bridge << SecureRandom.hex
}
when :w
if (data = writer.read)
routine.write(data)
end
end
}
Using bridges, the io routine is only called again when two conditions are met:
See the server example for a more complete use-case.
Goru was designed while writing a project in Go and imagining what Go-like concurrency might look like in Ruby.
FAQs
Unknown package
We found that goru demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.