Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails accepting duplicate POST requests

I have a strange problem with my rails application. My application accept duplicate POST requests within one second

This duplicate request, containing the same data, strangely able to bypass the uniqueness validation of my model. This results in creation of two rows of data with exact same contents.

What really baffled me is that it only happened once a day, starting yesterday, I am not sure what caused this. ( The system are already live, and being used by my clients, this method call is used 200-300 times a day, and I cannot reproduce this at all )

So here's the situation with my code snippent and link to the full code, with chronological order

  1. A Users want to create a new transaction, will call this method on the controller

    def new  
      @penjualan = Penjualan.new  
      @penjualan.kode_transaksi = "J"+ DateTime.now.strftime("%d%m%Y%H%M%S")+@active_user.id.to_s  
      @customers = Customer.all(:limit => cookies[:limit], :order=>:kode_kustomer )  
      @barangs = Barang.all(:limit => cookies[:limit] )  
      respond_to do |format|  
        format.html # new.html.erb  
        format.json { render json: @penjualan }  
      end     
    end  
    

    full controller on http://pastebin.com/Lmp7hncn line 648

  2. On the 'new' view, I have disabled the button with :disable_with, so that user cannot click on submit button twice, preventing user initiated double POST request

    .row  
      .span4  
        = f.submit 'Proses', :class=>"btn btn-large btn-primary", :disable_with => "Processing..."
    

    full view on http://pastebin.com/7b9W68RY line 97

  3. The Submitted request will call the 'create' method on controller, the same controller as #1, This method is called twice on 1 second difference. Even more strange is that this request bypass the uniqueness validation that I defined on the model, where it is supposed to fail the second request for having the same kode_transaksi as the first request

  4. I have a uniqueness constraints on my model (Penjualan) attributes (kode_transaksi)

    class Penjualan < ActiveRecord::Base  
      attr_accessible :customer_id, :jatuh_tempo, :kode_transaksi, :no_sj, :tanggal_bayar, :tanggal_transaksi, :total,:total_diskon, :ongkos, :user_id, :status_pembayaran, :is_returned, :kartu_kredit, :kartu_debit  
      has_many :detil_penjualans  
      attr_accessible :cash_total, :kembali  
      belongs_to :user  
      belongs_to :customer  
    
      validates :kode_transaksi, :uniqueness =>{:message=>"Transaksi Sudah Terjadi"}  
    
      scoped_search :on => [:kode_transaksi, :tanggal_transaksi, :status_pembayaran, :tanggal_bayar, :jatuh_tempo, :total ]  
      scoped_search :in => :customer, :on => [:nama_kustomer, :kode_kustomer]  
      scoped_search :in => :user, :on => [:username]  
    end  
    
  5. My Production log with snippet of the case

    Started POST "/penjualans" for 192.168.1.104 at 2012-11-24 12:15:40 +0900   
    Processing by PenjualansController#create as HTML     
    Parameters: {.... too long, see below ....}  
    
    
    Started POST "/penjualans" for 192.168.1.104 at 2012-11-24 12:15:41 +0900   
    Processing by PenjualansController#create as HTML     
    Parameters: {..... too long, see below ....}   
    Redirected to url/penjualans/17403   
    Completed 302 Found in 378ms (ActiveRecord: 246.0ms)   
    Redirected to url/penjualans/17404   
    Completed 302 Found in 367ms (ActiveRecord: 233.8ms)
    

Snippet of the logs http://pastebin.com/3tpua9gi

  1. This situation created a duplicate entry on my database which causes problem

I am really baffled with this behaviour and I'm at my wits end. Any help will be much appreciated.

like image 861
Steven St Avatar asked Nov 26 '12 12:11

Steven St


2 Answers

To quickly fix the problem I'd suggest you add a unique constraint to the database besides the model.

The rails docs suggest that uniqueness validation should be accompanied by a unique constraint in the database to prevent issues with two connections inserting the same unique value at the same time.

Other than that, is there maybe a problem with a user double-clicking the form in rapid succession? Maybe the disabling of the form does not work correctly and is therefore allowing users to click twice?

Is it every day at the same time or only at specific times?

like image 96
Tigraine Avatar answered Oct 19 '22 04:10

Tigraine


The issue is caused by the way in which the model-based uniqueness constraints are implemented in Rails. Basically, they work by asking the database if there are any existing rows for the given the uniqueness constraint and refusing the create the object if that is the case.

However, given the commonly used transactional isolation levels (typically repeatable-read) you can have overlapping transactions which both successfully check the constraint and then insert their objects without knowing from each other.

That is because to achieve actual uniqueness, you have to define your constraint in the database using UNIQUE indexes. This is much more important than defining the constraints in your model as only the database is able to ensure actual uniqueness by checking the constraint as a row is actually inserted/updated during multi-threaded operation.

About the only reason why you still want to additionally define the constraint in Ruby is that its error messages are much more friendly and you can thus handle the common case.

If the database constraint is hit rather than the Rails constraint, you'll just get false back when calling save without much information what went wrong except for a failed database constraint. The upside is however that you are guaranteed to still have a consistent database afterwards.

like image 3
Holger Just Avatar answered Oct 19 '22 06:10

Holger Just