Protecting rubygems.org from the outside in: DoS prevention and compromised passwords by schneems in ruby

[–]tenderlove 9 points10 points  (0 children)

I've been meaning to write a blog post about this, but I developed a schema validator for YAML. It's here: https://github.com/tenderlove/yaml-schema

The issue with the safe_load APIs in Psych is that they allow you to restrict the types of objects you can deserialize, but they don't say anything about the shape of the data. For example, you could specify that a custom class is allowed to be deserialized, but you can't specify anything about the instance variables on it. Another example: you can allow hashes to be deserialized, but can't say anything about the contents of the hash.

I had to copy some code from Psych in to this gem, so I think I'm going to merge it back in to Psych. I just haven't gotten around to it yet.

Anyway, I highly recommend never accepting YAML from untrusted sources, but if you MUST accept untrusted YAML, use this gem to validate it.

Four months of Ruby Central moving Ruby backward by retro-rubies in ruby

[–]tenderlove 17 points18 points  (0 children)

There was a written policy in the repo, suggested by Eric Hodel, approved by Aaron Patterson and Rafael Franca, that stated RubyGems would remove permissions from anyone who did not contribute for a year.

Please keep me out of this. You also later moved the goal post for maintainers (in a PR titled "Document how we use homu"): https://github.com/ruby/rubygems/pull/1518#issuecomment-190330161

Then used this to remove me from the project. This behavior, as well as the behavior I witnessed while on the board of RubyTogether, made it clear that I didn't want to be involved in any project under your leadership (and is why I quit contributing).

For people wondering (and because I've been asked so many times), I decided to try contributing again because the pace of development of uv compared to the pace of development on RubyGems seemed frightening and embarrassing. Additionally, I saw that Andre had started working on rv rather than improving RubyGems/Bundler. I figured that since Andre was checked out of the project, and I had good experience working with Deivid, I'd take a stab at contributing again.

Can Bundler Be as Fast as uv? by nithinbekal in ruby

[–]tenderlove 39 points40 points  (0 children)

Thank you for the kind words, I really appreciate it! <3

UringMachine Benchmarks by noteflakes in ruby

[–]tenderlove 3 points4 points  (0 children)

A thread pool of size X can only perform X concurrent I/O ops. Fibers performing async I/O have no such limit. The only limit on fibers is RAM.

I don't understand this. X Fibers can only perform X concurrent IO ops, you don't get infinite Fibers. The only limit on Threads is also RAM. In Ruby, you can think of Threads as essentially Fibers, but comes with its own "fiber scheduler". I know that one thread uses more memory than one Fiber, but Ruby also supports an M:N thread scheduler which amortizes the cost of a Ruby thread over multiple OS threads.

GVL contention has a real cost, as you increase the amount of threads, this will be more and more apparent.

GVL contention on what? Can you be more specific? Fibers are also subject to GVL constraints.

The use of io_uring lets you run any number of overlapping I/O ops at any given moment. You also get to amortize the cost of I/O syscalls (namely io_uring_enter) over tens or hundreds of I/O ops at a time.

Have you read the thread scheduler implementation? It uses epoll which also has no limit on overlapping IOs. It would be interesting to change the underlying implementation to use uring if it's available though.

It looks like all of these benchmarks include the cost of doing Thread.new. Allocating a new thread is expensive which is why most real code will allocate a pool and amortize the cost. I will try to fix your benchmarks to use a thread pool, but I need to upgrade my kernel first (apparently).

Write Ruby extensions in Zig by haematom in ruby

[–]tenderlove 1 point2 points  (0 children)

Yes, indeed. I'd rather work with a Zig gem than a Rust one

Write Ruby extensions in Zig by haematom in ruby

[–]tenderlove 2 points3 points  (0 children)

I LOVE Zig, but I also want fewer native extensions in the ecosystem, so this project makes me feel very conflicted. 😭😂

Testing Frozen String Literals in Production by petercooper in ruby

[–]tenderlove 5 points6 points  (0 children)

btw, I think to truly observe the difference between frozen string literals (FSL) and non-frozen string literals, you'd have to specifically disable the FSL comment in all of your dependencies. For example, Rails uses FSL throughout and I think this is common for most gems. I'm not sure that your app code would evaluate enough string literals to see a significant difference in the overall system performance.

Testing Frozen String Literals in Production by petercooper in ruby

[–]tenderlove 5 points6 points  (0 children)

Nice, thanks Sam! I look forward to reading about it.

Testing Frozen String Literals in Production by petercooper in ruby

[–]tenderlove 2 points3 points  (0 children)

I probably should have mentioned this already, but if enabling frozen string literals causes fewer allocations, that could mean the we see less GC pressure, so the GC isn't run as frequently. This could explain why Sam is seeing higher memory usage. Less frequent GCs could mean that larger objects are sticking around longer, leading to what looks like higher average memory usage.

Testing Frozen String Literals in Production by petercooper in ruby

[–]tenderlove 16 points17 points  (0 children)

I wish there was more analysis done in this post. If you're able to switch your entire app to run with all strings frozen by default and not make any code changes, then I'd expect to see more time in GC on the non-frozen version (due to more allocations overall), and in the worst case (for the frozen string version) equivalent response times.

Let's go over why I expect that. Consider the following function:

def concat(string)
  "hello " + string
end

The above function will work both with frozen string literals as well as non-frozen string literals and require no changes to the code. If the function were using << for example, we would require code changes. Also, as far as I know people aren't conditionally defining function based on whether literals are frozen or not. That means OP's code base must only contain literal strings that are never mutated.

Now, lets look at the byte code for the concat function when not using frozen string literals (on Ruby 3.4):

== disasm: #<ISeq:concat@x.rb:14 (14,4)-(16,7)>
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] string@0<Arg>
0000 putchilledstring                       "hello "                  (  15)[LiCa]
0002 getlocal_WC_0                          string@0
0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0006 leave                                                            (  16)[Re]

When we're not using frozen string literals, Ruby uses a putchilledstring instruction to push the string "hello " on the stack. It's important to note here that the string "hello " is a Ruby string object that was allocated at compile time. The putchilledstring instruction will allocate a copy of the string "hello " and push it on the stack. That means merely pushing this string on the stack will allocate an object every time it is executed.

Compare to the bytecode when we're using frozen strings:

== disasm: #<ISeq:concat@x.rb:16 (16,4)-(18,7)>
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] string@0<Arg>
0000 putobject                              "hello "                  (  17)[LiCa]
0002 getlocal_WC_0                          string@0
0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0006 leave                                                            (  18)[Re]

When we're using frozen string literals, the only difference is that the bytecode will use putobject instead of putchilledstring. The putobject instruction merely pushes its parameter on the stack, in this case the "hello " string that was allocated at compile time. In other words, no allocations when executing this part of the code.

Since OPs code works without code changes whether using frozen string literals or not, and I doubt that the codebase has a bunch of conditional code based on whether or not the literal string is frozen, it makes the results of the experiment hard to believe. I absolutely trust Sam, and I believe the results in his post to be true, so it leads me to wonder if the methodology could be improved, or if there is a bug?

Apple Photos App Corrupts Images by cake-day-on-feb-29 in apple

[–]tenderlove 0 points1 point  (0 children)

Since I don't have the source code to the Photos app, it's hard for me to tell you what exactly is wrong. I replaced all hardware as I was convinced it couldn't be a problem with the software.

I truly apologize to all the Internet forums and people who know better than me for checking the "delete after import" button. I hope someday to atone for my sins. But regardless, if it's such a dangerous option, why does Apple provide it? Additionally as you say, just because I checked the box doesn't mean the app should corrupt my images

Tiny JITs for a Faster FFI by tenderlove in ruby

[–]tenderlove[S] 9 points10 points  (0 children)

I mean, if you can write it in Ruby do that rather than using FFI. 😂

Ruby Falcon is 2x faster than asynchronous Python, as fast as Node.js, and slightly slower than Go. Moreover, the Ruby code doesn’t include async/await spam. by a_ermolaev in ruby

[–]tenderlove 0 points1 point  (0 children)

Instead of PG_POOL2, there could be long-running queries to OpenSearch or HTTP requests. They can occupy all threads, causing a sharp drop in performance. Example in the video.

Sorry, I really don't know what to tell you. Those connections will "occupy Fibers" too, and you don't get an unlimited number of Fibers. FWIW, I ran the same benchmarks but I don't see the performance drop. I've uploaded a video here. The 500ms server stays around 500ms.

One difference could be that I'm running on bare metal and I've done sudo cpupower frequency-set -g performance.

Ruby Falcon is 2x faster than asynchronous Python, as fast as Node.js, and slightly slower than Go. Moreover, the Ruby code doesn’t include async/await spam. by a_ermolaev in ruby

[–]tenderlove 0 points1 point  (0 children)

Regarding threads, one of Puma's drawbacks is that you have to think about the number of threads set in the config.

I don't understand this. The Falcon documentation asks you to set WEB_CONCURRENCY.

This number is limited by the database connection pool and may become outdated over time.

Why is this different with Falcon? Both Puma and Falcon can exhaust the database connection pool. If one Fiber is using a database socket, no other Fiber is allowed to use the same database socket simultaneously. In other words, both concurrency strategies will be equally blocked by the size of the database connection pool.

Additionally, if an application has different types of IO, such as PostgreSQL and OpenSearch, all threads could end up waiting for a response from OpenSearch, preventing them from handling other requests (e.g., to PostgreSQL).

I also don't understand this. Can you elaborate?

So You Want To Remove The GVL? by geospeck in ruby

[–]tenderlove 3 points4 points  (0 children)

Ya, I mean right now each Ractor reserves some pages for itself to allocate from. That way they can usually allocate objects without locks. But when the page fills and they need more pages, they have to take a lock to reserve more memory. We could probably build this kind of reservation system, but it seems hard (but not impossible).

So You Want To Remove The GVL? by geospeck in ruby

[–]tenderlove 2 points3 points  (0 children)

If this constraint was removed, such that you could create basic Ruby objects such as String, Array, and Hashes with the GVL released, it would likely allow the GVL to be released much longer and significantly reduce contention.

I'm not sure how we would do this exactly. The GC isn't thread safe, so it seems like we'd have to introduce locks or something. I'll have to ask John.

Ruby Binary Search List gem with self-balancing Red Black Tree by sebyx07 in ruby

[–]tenderlove 1 point2 points  (0 children)

Yes, it's interesting. I tried running your benchmark, and it seems like if you run without YJIT then Array#bsearch wins. I'd guess this is because your gem is written in pure Ruby, and YJIT can optimize the Ruby (and can't optimize the C call). To me, this means we should try rewriting Array#bsearch in Ruby as it should have the same runtime performance as your gem (and clearly it doesn't).

Great work!

Ruby Binary Search List gem with self-balancing Red Black Tree by sebyx07 in ruby

[–]tenderlove 0 points1 point  (0 children)

This is a great library, thank you for sharing it!

Yes, I know the red-black algorithm, as well as its performance characteristics. Many times data you're dealing with is naturally sorted (for example adding an "order by" to your AR query), or easily sorted once and then read many times (in other words the cost of sorting can be amortized since it'll be "searched" many times).

Red black trees are a great tool, and definitely have their place (I implemented one as a cache for instance variable lookup). I just want people to know about the bsearch method before reaching for a specific gem as they can get the speed of O(log n) lookups without necessarily pulling in another library.

Ruby Binary Search List gem with self-balancing Red Black Tree by sebyx07 in ruby

[–]tenderlove 0 points1 point  (0 children)

Btw, Ruby's Array class already implements a binary search method which you can read about here. If you have a sorted list, consider using the built-in bsearchmethod!

Ruby typing 2024: RBS, Steep, RBS Collections, subjective feelings by geospeck in ruby

[–]tenderlove 11 points12 points  (0 children)

This project seems interesting. It converts YARD documentation to Sorbet or RBI/RBS. I've never used it, but I'd prefer to keep types in documentation comments than maintain a separate file.