List:Commits« Previous MessageNext Message »
From:Andrei Elkin Date:November 29 2007 5:20pm
Subject:Re: bk commit into 5.1 tree (aelkin:1.2610) BUG#31552
View as plain text  
Mats, hej.

Hope you are getting better!

A newer patch has been committed.

> Andrei Elkin wrote:
>> Mats, hej.
>>
>> Thanks for a good review!
>>
>> I've committed a fresh patch accounting your notes.
>>   
>
> OK, I'll have a look at it.
>
>> Here are my answers.
> [snip]
>>> Andrei Elkin wrote:
>>>     
> [snip]
>
>>>> +#define is_bit_set(i, b) (uint) ((sizeof((i))*8 > b)?       \
>>>> +                                 ((uchar*) (&(i)))[(b) / 8] \
>>>> +                                 & (1 << ((b) & 7))       
>  \
>>>> +                                 : 0)
>>>>       
>
> [snip]
>
>>> Although I think that macros or functions for this purpose is a good
>>> thing, these macros makes a large number of assumptions that is
>>> incorrect and cause confusing error or incorrect behavior.
>>>
>>>     
>>
>>   
>>>   1. It assumes that all bytes are 8 bits large, which is not the case.
>>>     
>>
>> Generally in the server's code we are better to have the only
>> assumption.  And this one as you must know is all bytes are 8 bits
>> large in my_bitmap.h,cc etc.
>>   

Well, I was deluded with the magic 8...
>
> Referring to another part of the code does not change the fact that
> it's not correct, but this is not the important part. I would accept
> that bytes are treated as 8-bit entities when it does not affect the
> correctness. Comparing with my_bitmap, there is a difference. In that
> case, the assumption of 8-bit bytes just lead to wasting memory,


>  but in this case, it will cause the computations to not work.

Finally I understood your point.
bitmap library exploits the magic 8, which makes the library
endian-sensetive.
Whereas in my macros there is a special out-of-range 
test (sizeof((i))*8 > b) where 8 serves for different purpose.

So, I accept your suggestion and will modify the out-of-range's test.


>  Still, I
> would not consider this as an important problem, if it were not for
> the issues below.


>
> [snip]
>>   
>>>   4. It is endian-dependent. For example is_bit_set(0x1, 0) will be
>>>      'false' on a big-endian machine, but not on a little-endian
>>>      machine.
>>>     
>>
>> A good catch.

>> You must have seen that macros i used as the template are vulnarable as well
>>
>> #define _bitmap_set_bit(MAP, BIT) (((uchar*)(MAP)->bitmap)[(BIT) / 8] \
>>                                   |= (1 << ((BIT) & 7)))
>>
>> And then they're same wat buggy, are not they?
>>   
>
> Actually not, they are storing and retrieving data in the same
> "faulty" way, so they compensate each others (now, I wouldn't write
> the code this way, but that is a different issue). However, in this
> case, you are writing an "int" and retrieving it as "char", which is
> the real problem.
>

However, the macros themselves neither store nor retrieve.

      [you wrote]

      4. It is endian-dependent. For example is_bit_set(0x1, 0) will be
     'false' on a big-endian machine, but not on a little-endian machine.
 
Hence, your p.4 applies for this existing macro

 #define _bitmap_is_set(MAP, BIT) (uint) (((uchar*)(MAP)->bitmap)[(BIT) / 8] \
                                         & (1 << ((BIT) & 7)))

as well: *MAP->bitmap = 0x1, BIT = 0.

The only way for the macro to be endian-agnostic is not use merely
(1 << BIT) shift and never locate the byte with []!
That's what you offered in your first review mail.


>>>   5. It does not work if something is passed as 'i' that has a size
>>>      larger than 'int' (e.g., unsigned long long).
>>>     
>>
>> What do you mean? There is the check
>>
>> sizeof((i))*8 > b
>>
>> so that out-of-range bit forces "not set" return - which is correct.
>> Although it'd better to return -1 to designate the error in
>> accessing the bit.
>> That's what I am adding to your versions below.
>>   
>
> Nope, that is not what I was referring to... here is a small program
> with a simplified version of the macro. Note that for the values
> below, sizeof(V) * 8 <= (N), so the "out of range" result would not be
> returned.
>
>    #define bit_is_set(V,N) ((V) & (1 << (N)) ? 1 : 0)
>
>    int main() {
>      printf("bit_is_set(%llx,%d): %d\n",
>             (1ULL << 34), 34, bit_is_set(1ULL << 34, 34));
>      return EXIT_SUCCESS;
>    }
>
> The output from this program is:
>
>    bit_is_set(400000000,34): 0

Sorry, I could not combine your p.5 with this example.

So we have N=34, V=400 000 000 and sizeof(V)=4, that's why
"sizeof(V) * 8 <= (N)", right? 
But I was expecting the reverse sizeof(V) * 8 > N as
there should be some hint towards "unsigned long long" ...

>
>
> [snip]
>>>   6. I would prefer to have a naming of the macros with a common
>>>      prefix, e.g., "bit_" since this is how all other naming is done
>>>      (e.g., "my_", "mysql_", "COM_", "SQLCOM_")
>>>     
>>
>> Okay. Let it be bit_is_set, bit_do_set, bit_do_clear.
>>
>>
>>   
>>>   7. IMHO, do_set_bit() and do_clear_bit() are statements and should
>>>      not have a value, but this is not important.
>>>     
>>
>> I think if a routine can fail it should return something. And
>> accessing a out-of-range bit is such a case.
>>   
>

> Then the return value should also be consistently checked.

Ok, I will add asserts.

>
> [snip]
>
>>> Also, what do you mean with "conspire" (apart from that
>>> it's a typo). "Conspire" basically means to plan something evil or
>>> illegal... I suggest to use another word for whatever you want to
>>> describe.
>>>     
>>
>> I agree, with the meaning.  As the word "failure" does not carry any
>> moral sense let's tolarate a metaphor which is placed here to make our
>> work less routine more intriguing.
>> Sometimes it's an art to make slave inconsistent :)!
>>   
>
> True. :)
>
> [snip]
>
>>>> @@ -7170,43 +7201,48 @@ Write_rows_log_event::do_before_row_oper
>>>>  {
>>>>    int error= 0;
>>>>  -  /*
>>>> -    We are using REPLACE semantics and not INSERT IGNORE semantics
>>>> -    when writing rows, that is: new rows replace old rows.  We need to
>>>> -    inform the storage engine that it should use this behaviour.
>>>> -  */
>>>> -
>>>> -  /* Tell the storage engine that we are using REPLACE semantics. */
>>>> -  thd->lex->duplicates= DUP_REPLACE;
>>>> +  if (is_bit_set(slave_exec_mode, SLAVE_MODE_IDEMPOTENT) != 0 ||
>>>> +      m_table->s->db_type()->db_type == DB_TYPE_NDBCLUSTER)
>>>>         
>>> Here you're hard-coding for the NBD engine, which is not flexible at
>>> all. I would suggest adding a property to the handler (e.g., a table
>>> flag or a virtual function) and check that instead. That would mean
>>> that other engines that need the same feature can just set that  flag
>>> or override that function.
>>>     
>>
>> There is a reason in what you are saying. Still, i think we should no rash
>> with generalizing basing on only once existing case, which is it's
>> only NDB that replicates Update on table with primary key as
>> Write_rows event. The feature you are asking would be really welcome
>> as soon as the
>> NDB's method will be acquired by some-engine else and we will have
>> material for thinking more carefully.
>> At this point of time I am not sure if instead we should introduce a flag in
>> Write_rows event to force its applying in replace i.e idempotent
>> fashion.
>> So let's keep it simple as it's done and I will add your todo comments.
>>   
>
> Acceptable, even though I think it would be better to add the flag.

ok

>
> [snip]
>
>>>> @@ -88,6 +88,16 @@ TYPELIB delay_key_write_typelib=
>>>>    delay_key_write_type_names, NULL
>>>>  };
>>>>  +const char *slave_exec_mode_names[]=
>>>> +{ "STRICT", "IDEMPOTENT", NullS };
>>>> +static const unsigned int slave_exec_mode_names_len[]=
>>>> +{ strlen("STRICT"), strlen("IDEMPOTENT"), 0 };
>>>>         
>>> Please don't use function calls (that is, strlen()) when doing static
>>> initialization. It's unnecessary and it can also cause portability
>>> problems. It is also contrary to how initialization is done
>>> elsewhere. Without having delved deeper into how the lengths are
>>> computed, there's plenty of code to compute the length of these fields
>>> when they are being used, so I think that you don't have to add the
>>> lengths explicitly.
>>>
>>> Could you check to see if this definition works?
>>>
>>>    TYPELIB slave_exec_mode_typelib=
>>>    {
>>>      array_elements(slave_exec_mode_names)-1, "",  slave_exec_mode_names,
> NULL
>>>    };
>>>
>>>      
>>>
>>> If it does, then you can remove the explicit length array. In either
>>> case, don't use strlen() inside a static initializer.
>>>
>>>     
>>
>> I don't see reason to check:
>>
>> typedef struct st_typelib {	/* Different types saved here */
>>   unsigned int count;		/* How many types */
>>   const char *name;		/* Name of typelib */
>>   const char **type_names;
>>   unsigned int *type_lengths;
>> } TYPELIB;
>>
>> We need type_lengths to reference to lengths.
>>   
>

> ... but it is not used elsewhere.

Of course it is!

TYPELIB sql_mode_typelib= { array_elements(sql_mode_names)-1,"",
			    sql_mode_names,
                            (unsigned int *)sql_mode_names_len };
where

static const unsigned int sql_mode_names_len[]=
{
  /*REAL_AS_FLOAT*/               13,
  ...

Imo sql_mode is the closest relative to the slave_exec_mode so that
one of the approches could be to extent sql_mode with the new options.

>
>> I see that all usages of declaring TYPELIB deals with
>> explicit numbers, which is awkward - i believe you agree.
>>   
>
> I don't see that. Here are a few excerpts from the server code:
>
> client/mysqladmin.c:
>
>    static TYPELIB command_typelib=
>    { array_elements(command_names)-1,"commands", command_names, NULL};
>
> client/mysqldump.c:
>
>    TYPELIB compatible_mode_typelib=
>    {array_elements(compatible_mode_names)-1, "", compatible_mode_names,
>    NULL};
>
> client/mysqltest.c:
>
>    TYPELIB command_typelib=
>    {array_elements(command_names),"", command_names, 0};
>
> sql-common/client.c:
>
>    static TYPELIB option_types={array_elements(default_options)-1,
>                     "options",default_options, NULL};
>    TYPELIB sql_protocol_typelib =
>    {array_elements(sql_protocol_names_lib)-1,"",
>                    sql_protocol_names_lib, NULL};
>
> sql/events.cc:
>
>    const TYPELIB Events::opt_typelib= {
>      array_elements(opt_event_scheduler_state_names)-1, "",
>      opt_event_scheduler_state_names, NULL
>    };
>
> sql/handler.cc:
>
>    TYPELIB tx_isolation_typelib= {
>      array_elements(tx_isolation_names)-1,"", tx_isolation_names, NULL
>    };
>
>
>
>> To address your concern i shall invoke sizeof() instead :)
>>   
>
> Why not stick to the established convention instead?

Well, there is no such convention, or at least there are two of them
:) One needs lengths the other not.


cheers,

Andrei
Thread
bk commit into 5.1 tree (aelkin:1.2610) BUG#31552Andrei Elkin20 Nov
  • Re: bk commit into 5.1 tree (aelkin:1.2610) BUG#31552Mats Kindahl22 Nov
    • Re: bk commit into 5.1 tree (aelkin:1.2610) BUG#31552Andrei Elkin27 Nov
      • Re: bk commit into 5.1 tree (aelkin:1.2610) BUG#31552Mats Kindahl27 Nov
        • Re: bk commit into 5.1 tree (aelkin:1.2610) BUG#31552Andrei Elkin29 Nov
          • Re: bk commit into 5.1 tree (aelkin:1.2610) BUG#31552Mats Kindahl30 Nov