File: cursor_reaping_spec.rb

package info (click to toggle)
ruby-mongo 2.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,020 kB
  • sloc: ruby: 110,810; makefile: 5
file content (143 lines) | stat: -rw-r--r-- 3,967 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# frozen_string_literal: true
# rubocop:todo all

require 'spec_helper'

describe 'Cursor reaping' do
  # JRuby does reap cursors but GC.start does not force GC to run like it does
  # in MRI, I don't currently know how to force GC to run in JRuby
  require_mri

# Uncomment for debugging this test.
=begin
  around(:all) do |example|
    saved_level = Mongo::Logger.logger.level
    Mongo::Logger.logger.level = Logger::DEBUG
    begin
      example.run
    ensure
      Mongo::Logger.logger.level = saved_level
    end
  end
=end

  let(:subscriber) { Mrss::EventSubscriber.new }

  let(:client) do
    authorized_client.with(max_pool_size: 10).tap do |client|
      client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
    end
  end

  let(:collection) { client['cursor_reaping_spec'] }

  before do
    data = [{a: 1}] * 10
    authorized_client['cursor_reaping_spec'].delete_many
    authorized_client['cursor_reaping_spec'].insert_many(data)
  end

  context 'a no-timeout cursor' do
    it 'reaps nothing when we do not query' do
      # this is a base line test to ensure that the reaps in the other test
      # aren't done on some global cursor
      expect(Mongo::Operation::KillCursors).not_to receive(:new)

      # just the scope, no query is happening
      collection.find.batch_size(2).no_cursor_timeout

      events = subscriber.started_events.select do |event|
        event.command['killCursors']
      end

      expect(events).to be_empty
    end

    def abandon_cursors
      [].tap do |cursor_ids|
        # scopes are weird, having this result in a let block
        # makes it not garbage collected
        10.times do
          scope = collection.find.batch_size(2).no_cursor_timeout

          # Begin iteration, creating the cursor
          scope.each.first

          scope.cursor.should_not be nil
          cursor_ids << scope.cursor.id
        end
      end
    end

    # this let block is a kludge to avoid copy pasting all of this code
    let(:cursor_id_and_kill_event) do
      expect(Mongo::Operation::KillCursors).to receive(:new).at_least(:once).and_call_original

      cursor_ids = abandon_cursors

      cursor_ids.each do |cursor_id|
        expect(cursor_id).to be_a(Integer)
        expect(cursor_id > 0).to be true
      end

      GC.start
      sleep 1

      # force periodic executor to run because its frequency is not configurable
      client.cluster.instance_variable_get('@periodic_executor').execute

      started_event = subscriber.started_events.detect do |event|
        event.command['killCursors']
      end
      started_event.should_not be nil

      found_cursor_id = nil
      started_event = subscriber.started_events.detect do |event|
        found = false
        if event.command['killCursors']
          cursor_ids.each do |cursor_id|
            if event.command['cursors'].map { |c| Utils.int64_value(c) }.include?(cursor_id)
              found_cursor_id = cursor_id
              found = true
              break
            end
          end
        end
        found
      end

      if started_event.nil?
        p subscriber.started_events
      end

      started_event.should_not be nil

      succeeded_event = subscriber.succeeded_events.detect do |event|
        event.command_name == 'killCursors' && event.request_id == started_event.request_id
      end

      expect(succeeded_event).not_to be_nil

      expect(succeeded_event.reply['ok']).to eq 1

      [found_cursor_id, succeeded_event]
    end

    it 'is reaped' do
      cursor_id_and_kill_event
    end

    context 'newer servers' do
      min_server_fcv '3.2'

      it 'is really killed' do
        cursor_id, event = cursor_id_and_kill_event

        expect(event.reply['cursorsKilled']).to eq([cursor_id])
        expect(event.reply['cursorsNotFound']).to be_empty
        expect(event.reply['cursorsAlive']).to be_empty
        expect(event.reply['cursorsUnknown']).to be_empty
      end
    end
  end
end