Skip to content

Commit

Permalink
Merge branch 'master' into csot
Browse files Browse the repository at this point in the history
  • Loading branch information
comandeo-mongo committed Jul 4, 2024
2 parents 03b3613 + 3c5dc93 commit b4d8a17
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 111 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: "Dry-Run Cleanup"
run-name: "Dry Run Cleanup for ${{ github.ref }}"

on:
workflow_dispatch:
inputs:
confirm:
description: Indicate whether you want this workflow to run (must be "true")
required: true
type: string
tag:
description: The name of the tag (and release) to clean up
required: true
type: string

jobs:
release:
name: "Dry-Run Cleanup"
environment: release
runs-on: 'ubuntu-latest'
if: ${{ inputs.confirm == 'true' }}

permissions:
# required for all workflows
security-events: write

# required to fetch internal or private CodeQL packs
packages: read

# only required for workflows in private repositories
actions: read
contents: write

# required by the mongodb-labs/drivers-github-tools/setup@v2 step
# also required by `rubygems/release-gem`
id-token: write

steps:
- name: "Run the cleanup action"
uses: mongodb-labs/drivers-github-tools/ruby/cleanup@v2
with:
app_id: ${{ vars.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
tag: ${{ inputs.tag }}
103 changes: 20 additions & 83 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
name: "Driver Release"
run-name: "Ruby Driver Release ${{ github.ref_name }}"
run-name: "Driver Release for ${{ github.ref }}"

on: workflow_dispatch
on:
workflow_dispatch:
inputs:
dry_run:
description: Whether this is a dry run or not
required: true
default: true
type: boolean

env:
SILK_ASSET_GROUP: mongodb-ruby-driver
RELEASE_MESSAGE_TEMPLATE: |
Version {0} of the [MongoDB Ruby Driver](https://rubygems.org/gems/mongo) is now available.
Expand Down Expand Up @@ -43,88 +51,17 @@ jobs:
id-token: write

steps:
- name: "Create temporary app token"
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: "Store GitHub token in environment"
run: echo "GH_TOKEN=${{ steps.app-token.outputs.token }}" >> "$GITHUB_ENV"
shell: bash

- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ env.GH_TOKEN }}

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true

- name: Setup GitHub tooling for DBX Drivers
uses: mongodb-labs/drivers-github-tools/setup@v2
- name: "Run the publish action"
uses: mongodb-labs/drivers-github-tools/ruby/publish@v2
with:
app_id: ${{ vars.APP_ID }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
aws_region_name: ${{ vars.AWS_REGION_NAME }}
aws_secret_id: ${{ secrets.AWS_SECRET_ID }}

- name: Get the driver version
shell: bash
run: |
echo "DRIVER_VERSION=$(ruby -Ilib -rmongo/version -e 'puts Mongo::VERSION')" >> "$GITHUB_ENV"
- name: Set output gem file name
shell: bash
run: |
echo "GEM_FILE_NAME=mongo-${{ env.DRIVER_VERSION }}.gem" >> "$GITHUB_ENV"
- name: Build the gem
shell: bash
run: |
gem build --output=${{ env.GEM_FILE_NAME }} mongo.gemspec
- name: Sign the gem
uses: mongodb-labs/drivers-github-tools/gpg-sign@v2
with:
filenames: '${{ env.GEM_FILE_NAME }}'

- name: Create and sign the tag
uses: mongodb-labs/drivers-github-tools/git-sign@v2
with:
command: "git tag -u ${{ env.GPG_KEY_ID }} -m 'Release tag for v${{ env.DRIVER_VERSION }}' v${{ env.DRIVER_VERSION }}"

- name: Push the tag to the repository
shell: bash
run: |
git push origin v${{ env.DRIVER_VERSION }}
- name: Create a new release
shell: bash
run: gh release create v${{ env.DRIVER_VERSION }} --title ${{ env.DRIVER_VERSION }} --generate-notes --draft

- name: Capture the changelog
shell: bash
run: gh release view v${{ env.DRIVER_VERSION }} --json body --template '{{ .body }}' >> changelog

- name: Prepare release message
shell: bash
run: |
echo "${{ format(env.RELEASE_MESSAGE_TEMPLATE, env.DRIVER_VERSION) }}" > release-message
cat changelog >> release-message
- name: Update release information
shell: bash
run: |
echo "RELEASE_URL=$(gh release edit v${{ env.DRIVER_VERSION }} --notes-file release-message)" >> "$GITHUB_ENV"
- name: Upload release artifacts
run: gh release upload v${{ env.DRIVER_VERSION }} ${{ env.GEM_FILE_NAME }} ${{ env.RELEASE_ASSETS }}/${{ env.GEM_FILE_NAME }}.sig

- name: Publish the gem
uses: rubygems/release-gem@v1
with:
await-release: false
dry_run: ${{ inputs.dry_run }}
gem_name: mongo
product_name: Ruby Driver
product_id: mongodb-ruby-driver
release_message_template: ${{ env.RELEASE_MESSAGE_TEMPLATE }}
silk_asset_group: ${{ env.SILK_ASSET_GROUP }}
12 changes: 12 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ task :build do
WARNING
end

# `rake version` is used by the deployment system so get the release version
# of the product beng deployed. It must do nothing more than just print the
# product version number.
#
# See the mongodb-labs/driver-github-tools/ruby/publish Github action.
desc "Print the current value of Mongo::VERSION"
task :version do
require 'mongo/version'

puts Mongo::VERSION
end

# overrides the default Bundler-provided `release` task, which also
# builds the gem. Our release process assumes the gem has already
# been built (and signed via GPG), so we just need `rake release` to
Expand Down
31 changes: 28 additions & 3 deletions lib/mongo/retryable/base_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def initialize(retryable)

private

# Indicate which exception classes that are generally retryable.
# Indicate which exception classes that are generally retryable
# when using modern retries mechanism.
#
# @return [ Array<Mongo:Error> ] Array of exception classes that are
# considered retryable.
Expand All @@ -58,18 +59,42 @@ def retryable_exceptions
Error::ConnectionPerished,
Error::ServerNotUsable,
Error::SocketError,
Error::SocketTimeoutError
Error::SocketTimeoutError,
].freeze
end

# Indicate which exception classes that are generally retryable
# when using legacy retries mechanism.
#
# @return [ Array<Mongo:Error> ] Array of exception classes that are
# considered retryable.
def legacy_retryable_exceptions
[
Error::ConnectionPerished,
Error::ServerNotUsable,
Error::SocketError,
Error::SocketTimeoutError,
Error::PoolClearedError,
Error::PoolPausedError,
].freeze
end


# Tests to see if the given exception instance is of a type that can
# be retried.
# be retried with modern retry mechanism.
#
# @return [ true | false ] true if the exception is retryable.
def is_retryable_exception?(e)
retryable_exceptions.any? { |klass| klass === e }
end

# Tests to see if the given exception instance is of a type that can
# be retried with legacy retry mechanism.
#
# @return [ true | false ] true if the exception is retryable.
def is_legacy_retryable_exception?(e)
legacy_retryable_exceptions.any? { |klass| klass === e }
end
# Logs the given deprecation warning the first time it is called for a
# given key; after that, it does nothing when given the same key.
def deprecation_warning(key, warning)
Expand Down
4 changes: 2 additions & 2 deletions lib/mongo/retryable/read_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,10 @@ def legacy_read_with_retry(session, server_selector, context = nil, &block)
context&.check_timeout!
attempt = attempt ? attempt + 1 : 1
yield select_server(cluster, server_selector, session)
rescue *retryable_exceptions, Error::OperationFailure::Family, Error::PoolError => e
rescue *legacy_retryable_exceptions, Error::OperationFailure::Family => e
e.add_notes('legacy retry', "attempt #{attempt}")

if is_retryable_exception?(e)
if is_legacy_retryable_exception?(e)
raise e if attempt > client.max_read_retries || session&.in_transaction?
elsif e.retryable? && !session&.in_transaction?
raise e if attempt > client.max_read_retries
Expand Down
57 changes: 34 additions & 23 deletions spec/integration/retryable_reads_errors_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,31 +74,42 @@
client.subscribe(Mongo::Monitoring::CONNECTION_POOL, subscriber)
end

it "retries on PoolClearedError" do
# After the first find fails, the pool is paused and retry is triggered.
# Now, a race is started between the second find acquiring a connection,
# and the first retrying the read. Now, retry reads cause the cluster to
# be rescanned and the pool to be unpaused, allowing the second checkout
# to succeed (when it should fail). Therefore we want the second find's
# check out to win the race. This gives the check out a little head start.
allow_any_instance_of(Mongo::Server::ConnectionPool).to receive(:ready).and_wrap_original do |m, *args, &block|
::Utils.wait_for_condition(5) do
# check_out_results should contain:
# - find1 connection check out successful
# - pool cleared
# - find2 connection check out failed
# We wait here for the third event to happen before we ready the pool.
cmap_events.select do |e|
event_types.include?(e.class)
end.length >= 3
shared_examples_for 'retries on PoolClearedError' do
it "retries on PoolClearedError" do
# After the first find fails, the pool is paused and retry is triggered.
# Now, a race is started between the second find acquiring a connection,
# and the first retrying the read. Now, retry reads cause the cluster to
# be rescanned and the pool to be unpaused, allowing the second checkout
# to succeed (when it should fail). Therefore we want the second find's
# check out to win the race. This gives the check out a little head start.
allow_any_instance_of(Mongo::Server::ConnectionPool).to receive(:ready).and_wrap_original do |m, *args, &block|
::Utils.wait_for_condition(5) do
# check_out_results should contain:
# - find1 connection check out successful
# - pool cleared
# - find2 connection check out failed
# We wait here for the third event to happen before we ready the pool.
cmap_events.select do |e|
event_types.include?(e.class)
end.length >= 3
end
m.call(*args, &block)
end
m.call(*args, &block)
threads.map(&:join)
expect(check_out_results[0]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckedOut)
expect(check_out_results[1]).to be_a(Mongo::Monitoring::Event::Cmap::PoolCleared)
expect(check_out_results[2]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckOutFailed)
expect(find_events.length).to eq(3)
end
threads.map(&:join)
expect(check_out_results[0]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckedOut)
expect(check_out_results[1]).to be_a(Mongo::Monitoring::Event::Cmap::PoolCleared)
expect(check_out_results[2]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckOutFailed)
expect(find_events.length).to eq(3)
end

it_behaves_like 'retries on PoolClearedError'

context 'legacy read retries' do

let(:client) { authorized_client.with(options.merge(retry_reads: false, max_read_retries: 1)) }

it_behaves_like 'retries on PoolClearedError'
end

after do
Expand Down

0 comments on commit b4d8a17

Please sign in to comment.